lovable skill · lovable-arc

The Arc / Circle skill, in full.

Everything that broke building on Arc testnet and mainnet with Circle wallets, Privy and USDC / EURC / cirBTC, plus the fixes. Lovable uses it automatically on Arc builds. Add it under Settings → Skills, then type / in chat to call it up.

Download .zip

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)

ThingTestnetMainnet
chainId / CAIP-25042002 / eip155:50420025042 / eip155:5042
RPChttps://rpc.testnet.arc.networkhttps://rpc.mainnet.arc.io (Alchemy optional, server-only ARC_MAINNET_RPC_URL)
Explorer (Blockscout)https://testnet.arcscan.apphttps://explorer.arc.io
Circle blockchain idARC-TESTNETARC
Circle API keyTESTLIVE
Fundinghttps://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

TokenTestnetMainnetDecimals
USDC0x3600000000000000000000000000000000000000same6 ERC-20 / 18 native
EURC0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c16
cirBTC0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF0x171A4217b86A807A64eB94757Db6849fb4bDbAA08

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)

  1. 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-roll fetch adapters against https://api.circle.com/v1/w3s; the whole client is ~150 lines and uses only Web Crypto. See references/circle-scp.md.
  2. feeLevel: "MEDIUM", never a gasPrice/fee object. Circle rejects EIP-1559-style fee payloads on Arc with an opaque 400.
  3. 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.
  4. VITE_* is a banned secret prefix in Lovable. Store as PRIVY_APP_ID, not VITE_PRIVY_APP_ID, and surface it through a route loader reading process.env. Never ship the Alchemy RPC URL to the browser — proxy it.
  5. Alchemy's Arc endpoint caps eth_getLogs at 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. See references/rpc-and-indexing.md.
  6. Pin solc@0.8.24 in devDependencies and use an exact pragma solidity 0.8.24;. A newer local compiler produces bytecode Blockscout cannot reproduce, and verification fails with no useful message.
  7. Wrap Privy in <ClientOnly> + React.lazy(). A static import of @privy-io/react-auth crashes workerd. defaultChain: { id: 5042002, name: "Arc Testnet" } as never — the Privy type is narrow.
  8. No gas sponsorship needed or available. USDC is gas on Arc; fund the embedded wallet from the faucet and sign normally. No paymaster policy.
  9. Write one JSON file per deployed contract (address, abi, chainId, explorer, deployTx) under src/data/ and have the UI read it. Deploys then go live without a code edit.
  10. 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.
  11. 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.
  12. 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.
  13. modal={false} on every Radix menu near sticky chrome. Modal mode locks <body> scroll and breaks a sticky header the moment the menu opens mid-scroll. See references/demo-ux.md.
  14. 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.
  15. 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.
  16. 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.
  17. 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.
  18. 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.
  19. Files over 10 MB never go in the repo. Large downloads (e.g. a 28 MB llms-full.txt) go to Lovable asset storage via lovable-assets create, referenced by .asset.json; serve /llms-full.txt as a redirect route. Generate llms files from the same constants as the prompts.

Reference index

Read thisFor
references/circle-scp.mdEntity secret onboarding, wallet bootstrap, transfer + contractExecution, polling, error mapping
references/rpc-and-indexing.mdRPC selection, getLogs limits, chunking, caching, Arcscan v2 as indexer
references/contracts-and-verify.mdsolc pinning, Blockscout verify recipe, ERC-721 / ERC-2981 / ERC-1271 on Arc
references/payments-ux.mdUSDC/EURC/cirBTC toggle, decimals, FX, Privy approval-modal gotchas, treasury panel
references/agent-negotiation.mdTwo-agent buyer/seller loops that close: derived budgets, seller floors, final-turn rules, deterministic fallback, quote sanitising
references/demo-ux.mdJudge-facing layer: collapsible run ledgers, cross-mode settlement list, catalog-derived prefills, Radix scroll-lock
references/networks.mdTestnet vs mainnet constants, decimals, the switch pattern
references/failure-modes.mdFull 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/total steps, one big "View receipt on Arcscan" button, payloads behind a payload · N fields disclosure 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

  1. Build the indexer read path and the error mapper on day one. Both were retrofitted after users hit rate limits and opaque 400s.
  2. Decide the contract JSON artifact shape before the first deploy — redeploys are cheap, but three UI refactors to chase a changed shape are not.
  3. Treat treasury gas as a first-class UI concern from the start: address, balance, and a lowGas warning. Payout failures otherwise look like bugs.
  4. Sketch the multi-token decimal matrix (6/6/8) once, in one TOKENS table, instead of discovering cirBTC's 8 decimals via a wrong balance.
  5. 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.
  6. 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.
  7. 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.
  8. 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

  1. bootstrap-circle.mjs prints treasury wallet id + address, and the raw entity-secret hex is on disk before any network call.
  2. Treasury funded at faucet.circle.com → Arc Testnet.
  3. deploy-arc.mjs polls to status: "COMPLETE" and writes src/data/<contract>.json with address + abi + chainId: 5042002.
  4. verify-arc.mjs returns 200; a minute later is_verified: true on https://testnet.arcscan.app/api/v2/smart-contracts/{address}.
  5. 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.