Lovable · Arc (Testnet + Mainnet)
Default to testnet; mainnet spends real USDC. Everything here is what actually broke shipping Arc apps on Lovable (four contracts, an agent payout engine, an NFT marketplace with royalties, a 996-prompt hackathon pack).
For CCTP bridging and UCP checkout schemas see dance-ucp-arc-circle. For the
SSR-safe Privy mount see evvm-privy-integration. This skill owns the
Lovable × Arc delta. Full network details: references/networks.md.
Networks (copy verbatim)
| Thing | Testnet | Mainnet |
|---|---|---|
| chainId / CAIP-2 | 5042002 / eip155:5042002 | 5042 / eip155:5042 |
| RPC | https://rpc.testnet.arc.network | https://rpc.mainnet.arc.io (Alchemy optional, server-only ARC_MAINNET_RPC_URL) |
| Explorer (Blockscout) | https://testnet.arcscan.app | https://explorer.arc.io |
| Circle blockchain id | ARC-TESTNET | ARC |
| Circle API key | TEST | LIVE |
| Funding | https://faucet.circle.com/ (~20 USDC/day) | none — real USDC |
RPC hostname trap (testnet). Docs interleave rpc.testnet.arc.io and
rpc.testnet.arc.network; only .network resolves reliably.
Decimals (corrected). Native USDC balance (eth_getBalance, viem
nativeCurrency) uses 18 decimals. The USDC ERC-20 interface at
0x3600…0000 uses 6. EURC 6, cirBTC 8. Earlier versions of this
skill said native = 6 — that was wrong and renders balances off by 10^12.
Token addresses
| Token | Testnet | Mainnet | Decimals |
|---|---|---|---|
| USDC | 0x3600000000000000000000000000000000000000 | same | 6 ERC-20 / 18 native |
| EURC | 0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a | 0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1 | 6 |
| cirBTC | 0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF | 0x171A4217b86A807A64eB94757Db6849fb4bDbAA0 | 8 |
Viem chains (src/lib/arc-chain.ts):
import { defineChain } from "viem";
const native = { name: "USD Coin", symbol: "USDC", decimals: 18 } as const;
export const arcTestnet = defineChain({ id: 5042002, name: "Arc Testnet", nativeCurrency: native,
rpcUrls: { default: { http: ["https://rpc.testnet.arc.network"] } },
blockExplorers: { default: { name: "Arcscan", url: "https://testnet.arcscan.app" } }, testnet: true });
export const arcMainnet = defineChain({ id: 5042, name: "Arc", nativeCurrency: native,
rpcUrls: { default: { http: ["https://rpc.mainnet.arc.io"] } },
blockExplorers: { default: { name: "Arc Explorer", url: "https://explorer.arc.io" } } });
Testnet/mainnet switch
One NETWORKS table (chain, tokens, explorer, faucet, Circle id) + server
ARC_NETWORK env (default testnet) + a localStorage NetworkProvider in the
UI. Key Privy by network (key={network}, supportedChains: [arcTestnet, arcMainnet]). Gate contracts not yet deployed on mainnet with a "switch back
to testnet" card. Keep the Alchemy mainnet URL server-only (proxy via a
server fn). Hide the faucet link on mainnet and show a real-money warning.
Participant setup (2 pasted secrets)
Participants paste only CIRCLE_API_KEY + PRIVY_APP_ID. Lovable runs
scripts/bootstrap-circle.mjs in the sandbox: generate the 32-byte entity
secret → write it to disk with the wx flag before any network call →
RSA-OAEP register via POST (not PUT) → create the treasury wallet → print
the address to fund. A 409 means the key was auto-primed: register once via the
Console configurator. Paste the 64-hex secret, never the recovery blob. SCP
deploys need an idempotencyKey and abiJson as a stringified JSON. The
whole build must fit in ~5 Lovable credits (single page, minimal Solidity).
Hard rules (each one cost hours)
- Never install
@circle-fin/*SDKs. They assume Node and crash the Cloudflare Worker runtime the app deploys to ([unenv] … not implemented,__dirname is not defined, or a blank 500). Hand-rollfetchadapters againsthttps://api.circle.com/v1/w3s; the whole client is ~150 lines and uses only Web Crypto. Seereferences/circle-scp.md. feeLevel: "MEDIUM", never agasPrice/fee object. Circle rejects EIP-1559-style fee payloads on Arc with an opaque 400.- Re-encrypt the entity secret on every request. Fetch
/config/entity/publicKey, RSA-OAEP(SHA-256) the 32-byte secret, base64 it. Reused ciphertext is rejected. VITE_*is a banned secret prefix in Lovable. Store asPRIVY_APP_ID, notVITE_PRIVY_APP_ID, and surface it through a route loader readingprocess.env. Never ship the Alchemy RPC URL to the browser — proxy it.- Alchemy's Arc endpoint caps
eth_getLogsat 10 blocks (free tier) and rate-limits wide sweeps. Use the public Arc RPC for log reads, chunk the window, cap the lookback, and cache. Better: read history from the Arcscan v2 REST API. Seereferences/rpc-and-indexing.md. - Pin
solc@0.8.24in devDependencies and use an exactpragma solidity 0.8.24;. A newer local compiler produces bytecode Blockscout cannot reproduce, and verification fails with no useful message. - Wrap Privy in
<ClientOnly>+React.lazy(). A static import of@privy-io/react-authcrashes workerd.defaultChain: { id: 5042002, name: "Arc Testnet" } as never— the Privy type is narrow. - No gas sponsorship needed or available. USDC is gas on Arc; fund the embedded wallet from the faucet and sign normally. No paymaster policy.
- Write one JSON file per deployed contract (
address,abi,chainId,explorer,deployTx) undersrc/data/and have the UI read it. Deploys then go live without a code edit. - Demo-fallback over hard failure. Every server module must boot with
zero secrets and return a realistic envelope flagged
simulated: true. Never throw at module scope for missing config. - Agent-demo outcomes come from deterministic code, not prompt wording.
Prompts shape the transcript; a post-loop fallback that promotes the best
in-policy quote guarantees the close. Never let an LLM own your success
rate. See
references/agent-negotiation.md. - Any price an LLM emits is untrusted input. Normalise minor-units vs
decimals, clamp to
[floor, list], and re-derive the charged total from the catalog before it reaches a settlement path. modal={false}on every Radix menu near sticky chrome. Modal mode locks<body>scroll and breaks astickyheader the moment the menu opens mid-scroll. Seereferences/demo-ux.md.- Every settlement path writes to one shared client ledger. Add the
record()call in the same edit as the settlement itself. A judge needs one list — across H2H/H2A/A2A/A2H — proving money moved, with Arcscan links and confirmed/pending status from the Arcscan v2 API. - Keep the cart drawer focused on checkout. Do not inline transaction
history, settlement lists, or mode-switching chrome inside a cart drawer.
A compact link to the dedicated settlement surface (
/judge,/shop, or a standalone history page) keeps the checkout path short and the receipt surface authoritative. - Mode-independent editorial/reference pages. Routes that explain the
product (
/markets,/moves,/primer,/deck,/judge) must always render their own content. Never wrap them in a per-mode surface, and hide the mode toggle on them so a persisted GX mode does not silently swap the page in the middle of a demo. - Paginate long settlement/activity lists. Judge ledgers, receipt panels, and marketplace activity lists should paginate at ~10 items with numbered controls. Long scrollable lists push primary actions below the fold and break demo pacing.
- Round hero stats to 2 decimals. Marketing metrics (e.g., "USDC settled through the rail") should render with 2-decimal precision, not raw contract precision, for readable social proof.
- Files over 10 MB never go in the repo. Large downloads (e.g. a
28 MB
llms-full.txt) go to Lovable asset storage vialovable-assets create, referenced by.asset.json; serve/llms-full.txtas a redirect route. Generate llms files from the same constants as the prompts.
Reference index
| Read this | For |
|---|---|
references/circle-scp.md | Entity secret onboarding, wallet bootstrap, transfer + contractExecution, polling, error mapping |
references/rpc-and-indexing.md | RPC selection, getLogs limits, chunking, caching, Arcscan v2 as indexer |
references/contracts-and-verify.md | solc pinning, Blockscout verify recipe, ERC-721 / ERC-2981 / ERC-1271 on Arc |
references/payments-ux.md | USDC/EURC/cirBTC toggle, decimals, FX, Privy approval-modal gotchas, treasury panel |
references/agent-negotiation.md | Two-agent buyer/seller loops that close: derived budgets, seller floors, final-turn rules, deterministic fallback, quote sanitising |
references/demo-ux.md | Judge-facing layer: collapsible run ledgers, cross-mode settlement list, catalog-derived prefills, Radix scroll-lock |
references/networks.md | Testnet vs mainnet constants, decimals, the switch pattern |
references/failure-modes.md | Full symptom → cause → fix table |
What worked well (reuse these)
- Circle SCP as the only deploy path. No EOA, no funded private key in CI, USDC gas from the treasury wallet. Four contracts shipped this way.
- ERC-1271 authorizer contract so agent/treasury actions are authorized by a contract signature instead of an EOA delegate key.
- ERC-2981 royalties enforced inside the marketplace
buy(), atomically, in whichever payment token the buyer selected — a separate royalty leg that can fail independently is a worse demo and a worse product. - Off-chain session ledger that batches nano-amounts and settles to Arc at a threshold (~$0.50). A 20 USDC/day faucet then survives a full day of demos; size each unit at ~$0.001.
- Arcscan v2 REST as the indexer for activity/receipt panels — free, full history, no client-side log scan.
- Client-side UnixFS CIDv1 computation before pinning, so the user sees the content hash and can verify the pin matched.
- A human-readable error mapper (
mapChainError) between chain/Circle errors and UI copy. Retrofitting this late was the single biggest time sink. - Run ledger = summary bar + collapsed payloads. State chip,
n/totalsteps, one big "View receipt on Arcscan" button, payloads behind apayload · N fieldsdisclosure that auto-opens only on failures. - A single cross-mode settlement list. H2H, H2A, A2A and A2H all write to one persisted client ledger; judges stop asking whether anything settled.
- Mode-independent editorial pages + hidden mode toggle. Content routes stayed readable even when the user had a persisted GX mode, and the compact "Recent settlements →" link kept the cart drawer focused on checkout.
- Paginated settlement/activity lists. 10-item pages with numbered controls kept the judge demo compact and the primary action always visible.
What I'd do differently next time
- Build the indexer read path and the error mapper on day one. Both were retrofitted after users hit rate limits and opaque 400s.
- Decide the contract JSON artifact shape before the first deploy — redeploys are cheap, but three UI refactors to chase a changed shape are not.
- Treat treasury gas as a first-class UI concern from the start: address,
balance, and a
lowGaswarning. Payout failures otherwise look like bugs. - Sketch the multi-token decimal matrix (6/6/8) once, in one
TOKENStable, instead of discovering cirBTC's 8 decimals via a wrong balance. - Build the deterministic negotiation fallback before tuning any prompt. Days went into prompt wording for a two-agent demo that a 15-line best-in-policy promotion fixed outright.
- Ship the shared settlement ledger with the first settlement path, not after a reviewer asks where the transactions are. Retrofitting it meant touching every mode's success handler at once.
- Decide which routes are mode-driven vs. mode-independent before wiring the global toggle. Retrofitting content pages to ignore the saved mode meant touching route wrappers, headers, and mobile drawers at once.
- Build pagination into the first settlement/activity panel, not after the list grows long. A 10-item default with numbered controls keeps demos from turning into scrolling exercises.
Success checkpoints
bootstrap-circle.mjsprints treasury wallet id + address, and the raw entity-secret hex is on disk before any network call.- Treasury funded at
faucet.circle.com→ Arc Testnet. deploy-arc.mjspolls tostatus: "COMPLETE"and writessrc/data/<contract>.jsonwithaddress+abi+chainId: 5042002.verify-arc.mjsreturns 200; a minute lateris_verified: trueonhttps://testnet.arcscan.app/api/v2/smart-contracts/{address}.- Google login via Privy provisions an embedded wallet on 5042002 and a real tx confirms on Arcscan under that address.
Out of scope
CCTP bridging (dance-ucp-arc-circle). UCP checkout schemas
(same skill). Hardhat + funded-EOA deploys — Arc's pitch is "no EOA, USDC gas
from a Circle wallet"; use SCP.