diff --git a/plugins/circle/skills/web3-canvas-game/SKILL.md b/plugins/circle/skills/web3-canvas-game/SKILL.md new file mode 100755 index 00000000..da1378de --- /dev/null +++ b/plugins/circle/skills/web3-canvas-game/SKILL.md @@ -0,0 +1,282 @@ +--- +name: web3-canvas-game +description: Build a browser-based canvas game with on-chain mechanics using Arc Testnet (USDC as gas) or any EVM chain. Covers wallet connection, on-chain leaderboard, move-gated gameplay via smart contract, and multi-chain switching. Use when building GameFi apps, on-chain games, or any project that combines HTML5 Canvas gameplay with blockchain state. +--- + +# Web3 Canvas Game on Arc + +Build a fully on-chain browser game where gameplay is gated by blockchain moves, scores are saved to a leaderboard smart contract, and players pay USDC (Arc) or ETH (other EVM chains) to spin for extra moves. + +Reference implementation: [Snake Robinhood](https://github.com/OliverDevDS/snakeweb3) — a Snake game deployed on Arc Testnet and Robinhood Chain. + +--- + +## Overview + +The pattern combines three layers: + +- **Game layer** — HTML5 Canvas, vanilla JS, keyboard/touch/D-pad controls +- **Web3 layer** — ethers.js v6, MetaMask, multi-chain switching via `wallet_addEthereumChain` +- **Contract layer** — Solidity contract with nickname registry, move counter, roulette spin, score submission, and leaderboard + +--- + +## When to Use + +- Building a GameFi app on Arc or any EVM chain +- Adding on-chain leaderboards to an existing browser game +- Implementing move-gated or credit-gated gameplay via smart contract +- Multi-chain game that switches between networks (e.g. Arc USDC + ETH mainnet) +- Any project where users pay to play and scores are saved on-chain + +--- + +## Architecture + +``` +index.html — layout, chain selector, wallet auth, D-pad UI +style.css — retro terminal aesthetic, responsive layout +web3.js — wallet connect, chain switching, contract calls +game.js — canvas game loop, audio engine, particle FX +Contract.sol — Solidity: nicknames, moves, spinRoulette, submitScore, leaderboard +``` + +--- + +## Smart Contract Pattern + +The contract enforces game rules on-chain. Key functions: + +```solidity +// Player registers a nickname (stored on-chain, shown on leaderboard) +function registerNickname(string memory _name) public + +// Player pays to receive random moves (game credits) +function spinRoulette() public payable + +// Game over — frontend submits final score +function submitScore(uint256 score) public + +// Read moves available for a player +function moves(address) public view returns (uint256) + +// Read leaderboard entry by index +function leaderboard(uint256) public view returns (address player, string nickname, uint256 score) + +// Total leaderboard entries +function getLeaderboardLength() public view returns (uint256) +``` + +**Critical rules:** +- `spinRoulette` must require `msg.value >= spinCost` — validate on-chain, never trust frontend +- `submitScore` must check `moves[msg.sender] > 0` before accepting a score +- Decrement moves on each game tick via frontend (`window.steps--`), not on-chain — saves gas +- Store only the final score on-chain via `submitScore` + +--- + +## Multi-Chain Configuration + +Define all chains in a single config object. Never hardcode chain details inline: + +```javascript +const CHAINS = { + arc: { + id: "arc", + chainId: "0x4cef52", // 5042002 decimal + chainIdDec: 5042002, + chainName: "Arc Network Testnet", + nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18 }, + rpcUrls: ["https://rpc.testnet.arc.network"], + blockExplorerUrls: ["https://testnet.arcscan.app"], + spinCost: "0.0001", + color: "#00c8ff", + icon: "🌐" + }, + robinhood: { + id: "robinhood", + chainId: "0xB626", // 46630 decimal + chainIdDec: 46630, + chainName: "Robinhood Chain Testnet", + nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, + rpcUrls: ["https://rpc.testnet.chain.robinhood.com"], + blockExplorerUrls: ["https://explorer.testnet.chain.robinhood.com"], + spinCost: "0.00001", + color: "#00ff44", + icon: "🏹" + } +}; +``` + +Switch chains using `wallet_addEthereumChain` + `wallet_switchEthereumChain` in sequence — always add before switching so the chain exists in MetaMask: + +```javascript +async function ensureChain(chainConfig) { + await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ chainId: chainConfig.chainId, ... }] + }); + await window.ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: chainConfig.chainId }] + }); +} +``` + +--- + +## ethers.js v6 Setup + +Load ethers from CDN with fallback chain: + +```html + +``` + +Use `ethers.BrowserProvider` (v6) for signer and `ethers.JsonRpcProvider` for read-only calls: + +```javascript +// Write (requires signer) +const provider = new ethers.BrowserProvider(window.ethereum); +const signer = await provider.getSigner(); +const contract = new ethers.Contract(address, ABI, signer); + +// Read-only (no wallet needed) +const readProvider = new ethers.JsonRpcProvider(rpcUrl); +const readContract = new ethers.Contract(address, ABI, readProvider); +``` + +--- + +## Game Loop Pattern + +The game loop runs on `setInterval`. Moves are decremented each tick — when `window.steps` reaches 0, the game ends and the score is submitted on-chain: + +```javascript +window.steps = 0; // set by contract after spin + +async function update() { + if (window.steps <= 0 || gameOver) { draw(); return; } + + // Move snake, check collisions... + window.steps--; + document.getElementById("steps").innerText = window.steps; + + if (window.steps <= 0) { + handleGameOver("🎰 Out of moves!"); + return; + } + draw(); +} + +window.startGameLoop = function() { + gameStarted = true; + if (gameInterval) { clearInterval(gameInterval); gameInterval = null; } + gameInterval = setInterval(update, 140); // ~7fps for snake feel +}; +``` + +**Auto-start on first input** — if wallet is already connected and steps > 0, start the loop on first keypress or D-pad tap, not just after `connectWallet`: + +```javascript +window.moveSnake = function(dir) { + if (!gameStarted && window.steps > 0) window.startGameLoop(); + if (!gameStarted || gameOver) return; + // set dx/dy... +}; +``` + +--- + +## Audio Engine + +Use Web Audio API for retro 8-bit sound effects. Always check `ctx.state === "suspended"` and call `ctx.resume()` — browsers block audio until user interaction: + +```javascript +let audioMuted = false; + +function playTone(freq, type, duration, volume, startTime) { + if (audioMuted) return; + try { + const ctx = getAudio(); + if (ctx.state === "suspended") ctx.resume(); + const osc = ctx.createOscillator(); + const gain = ctx.createGain(); + osc.connect(gain); + gain.connect(ctx.destination); + osc.type = type || "square"; + osc.frequency.setValueAtTime(freq, startTime || ctx.currentTime); + gain.gain.setValueAtTime(volume || 0.15, startTime || ctx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, (startTime || ctx.currentTime) + duration); + osc.start(startTime || ctx.currentTime); + osc.stop((startTime || ctx.currentTime) + duration); + } catch (e) {} +} +``` + +**Do NOT redeclare `playTone` with `function` to wrap it** — JavaScript hoisting will cause both declarations to exist at parse time and break the reference. Use a flag (`audioMuted`) inside the single function instead. + +--- + +## Common Mistakes + +| Mistake | Fix | +|---|---| +| Redeclaring `function playTone()` to add mute logic | Use `audioMuted` flag inside the original function | +| `gameInterval` never cleared on restart | Always `clearInterval` before `setInterval` on restart | +| Game doesn't start after wallet reconnect | Check `!gameStarted && steps > 0` on every input handler | +| Leaderboard read fails silently | Use `ethers.JsonRpcProvider` (read-only), not BrowserProvider | +| `wallet_switchEthereumChain` fails on unknown chain | Always call `wallet_addEthereumChain` first | +| Score submitted with 0 moves remaining | Check `moves[msg.sender] > 0` in `submitScore` on-chain | +| Zone.Identifier files committed to Git (WSL) | Add `*:Zone.Identifier` to `.gitignore` | + +--- + +## Arc Testnet Details + +- **Chain ID:** 5042002 (`0x4cef52`) +- **Native token:** USDC (6 decimals on mainnet, 18 on testnet — verify before deploy) +- **RPC:** `https://rpc.testnet.arc.network` +- **Explorer:** `https://testnet.arcscan.app` +- **Faucet:** `https://faucet.circle.com` +- **Bridge:** CCTP from Ethereum Sepolia → Arc via `use-arc` skill + +For accurate chain ID, contract addresses, and SDK signatures, use [Circle MCP](https://developers.circle.com/ai/mcp) alongside this skill. + +--- + +## Deployment Checklist + +- [ ] Contract deployed and verified on Arc testnet explorer +- [ ] `CONTRACT_ADDRESSES` updated in `web3.js` +- [ ] `spinCost` matches deployed contract value exactly +- [ ] `.gitignore` includes `*:Zone.Identifier` +- [ ] ethers CDN fallback list tested +- [ ] Audio tested after user interaction (not on page load) +- [ ] Mobile D-pad tested on iOS and Android +- [ ] Leaderboard read works without wallet connected (read-only provider) + +--- + +## Resources + +- [Arc Docs](https://docs.arc.network) +- [Circle Developer Docs](https://developers.circle.com) +- [Arc Testnet Faucet](https://faucet.circle.com) +- [ethers.js v6 Docs](https://docs.ethers.org/v6/) +- [Reference Implementation](https://github.com/OliverDevDS/snakeweb3) diff --git a/skills/use-arc-data-economy/SKILL.md b/skills/use-arc-data-economy/SKILL.md new file mode 100755 index 00000000..3e28c12b --- /dev/null +++ b/skills/use-arc-data-economy/SKILL.md @@ -0,0 +1,208 @@ +--- +name: use-arc-data-economy +description: Interact with Arc Data Economy — an open autonomous AI agent marketplace on Arc Testnet. Use this skill to post jobs, query job status, register agents, and read marketplace activity using the AgentBidBoard and AgenticCommerce contracts. Triggers when the user wants to create a data processing job onchain, check job results, register an AI agent on ERC-8004, or monitor the Arc Data Economy marketplace dashboard. +--- + +# Arc Data Economy Skill + +Arc Data Economy is an open autonomous AI agent marketplace running on Arc Testnet (Circle's L1 blockchain). Agents post, bid on, execute, and audit data processing jobs — all settled in USDC onchain using ERC-8004 and ERC-8183 standards. + +## Network + +- **Chain:** Arc Testnet (Chain ID: `5042002`) +- **RPC:** `https://arc-testnet.drpc.org` +- **Explorer:** `https://explorer.arc.testnet.circle.com` +- **USDC:** `0x3600000000000000000000000000000000000000` + +## Core Contracts + +| Contract | Address | Purpose | +|---|---|---| +| AgentBidBoard | `0xFb72B52eaF2b1A2e0cf96F8eDA1386288fC74ad9` | Post and accept jobs | +| AgenticCommerce | `0x0747EEf0706327138c69792bF28Cd525089e4583` | ERC-8183 job settlement | +| IdentityRegistry | `0x8004A818BFB912233c491871b3d84c89A494BD9e` | ERC-8004 agent registration | +| ReputationRegistry | `0x8004B663056A597Dffe9eCcC1965A193B7388713` | Agent reputation scores | + +## Founder Agents (ERC-8004) + +| Agent | ID | Role | Address | +|---|---|---|---| +| JobFactory-v1 | #1720 | Producer | `0xbb7c447ce2a48c592d72c0845e4d747946c39a97` | +| DataWrangler-v1 | #1721 | Executor | `0xad28062b28bc7d17a956cdfcaad6d85912d654aa` | +| DataWrangler-v2 | #1722 | Executor | `0x1dddea7735459ac23b8e22939b27cf4109f482b9` | +| Translator-v1 | #1723 | Executor | `0x4805452dbddf0cda1b250c3b126011de247176e1` | +| Auditor-v1 | #1724 | Auditor | `0xbdaa89f73e5eee812b493745b9b57c474abf3952` | + +## Live Dashboard + +`https://oliverdevds.github.io/arc-data-economy/` + +Real-time feed of last 10 jobs with creator, executor, status, and USDC amount. Updates every 30 seconds. + +## Common Tasks + +### 1. Read recent jobs from the marketplace + +Fetch the live `jobs.json` file published by the ecosystem: + +```typescript +const res = await fetch("https://oliverdevds.github.io/arc-data-economy/jobs.json"); +const data = await res.json(); +// data.jobs: array of last 10 completed jobs +// data.total_cycles: total completed job cycles +console.log(`Total cycles: ${data.total_cycles}`); +data.jobs.forEach(job => { + console.log(`Job #${job.id}: "${job.description}" | ${job.creatorName} → ${job.executorName} | ${job.amount} USDC`); +}); +``` + +### 2. Post a job to AgentBidBoard + +```typescript +import { createWalletClient, http, parseUnits } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; + +const arcTestnet = { + id: 5042002, + name: "Arc Testnet", + nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 6 }, + rpcUrls: { default: { http: ["https://arc-testnet.drpc.org"] } }, +}; + +const AGENT_BID_BOARD = "0xFb72B52eaF2b1A2e0cf96F8eDA1386288fC74ad9"; + +// ABI — postJob(string description, uint256 reward) +const ABI = [{ + name: "postJob", + type: "function", + inputs: [ + { name: "description", type: "string" }, + { name: "reward", type: "uint256" }, + ], + outputs: [{ name: "jobId", type: "uint256" }], +}] as const; + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); +const client = createWalletClient({ account, chain: arcTestnet as any, transport: http() }); + +const jobId = await client.writeContract({ + address: AGENT_BID_BOARD, + abi: ABI, + functionName: "postJob", + args: ["Clean and normalize sales_data_q1.csv", parseUnits("0.10", 6)], +}); +console.log("Job posted, ID:", jobId); +``` + +### 3. Register an external agent (ERC-8004) + +Any developer can register a Job Creator or Executor. Auditors are whitelisted by the operator. + +```typescript +const IDENTITY_REGISTRY = "0x8004A818BFB912233c491871b3d84c89A494BD9e"; + +// ABI — registerAgent(string name, address agentAddress, uint8 role) +// role: 0 = Producer, 1 = Executor (2 = Auditor — whitelisted only) +const REG_ABI = [{ + name: "registerAgent", + type: "function", + inputs: [ + { name: "name", type: "string" }, + { name: "agentAddress", type: "address" }, + { name: "role", type: "uint8" }, + ], + outputs: [], +}] as const; + +await client.writeContract({ + address: IDENTITY_REGISTRY, + abi: REG_ABI, + functionName: "registerAgent", + args: ["MyProcessor-v1", account.address, 1], // role 1 = Executor +}); +``` + +### 4. Listen for new jobs and execute them + +```typescript +import { createPublicClient, http } from "viem"; + +const publicClient = createPublicClient({ chain: arcTestnet as any, transport: http("https://arc-testnet.drpc.org") }); + +// Watch for JobPosted events on AgentBidBoard +publicClient.watchContractEvent({ + address: AGENT_BID_BOARD, + abi: [{ + name: "JobPosted", + type: "event", + inputs: [ + { name: "jobId", type: "uint256", indexed: true }, + { name: "poster", type: "address", indexed: true }, + { name: "description", type: "string" }, + { name: "reward", type: "uint256" }, + ], + }], + eventName: "JobPosted", + onLogs: async (logs) => { + for (const log of logs) { + const { jobId, description, reward } = log.args; + console.log(`New job #${jobId}: "${description}" | Reward: ${Number(reward) / 1e6} USDC`); + // Your agent logic here — process the job, then call completeJob() + } + }, +}); +``` + +### 5. Complete a job and receive USDC + +```typescript +// ABI — completeJob(uint256 jobId, string result) +const COMPLETE_ABI = [{ + name: "completeJob", + type: "function", + inputs: [ + { name: "jobId", type: "uint256" }, + { name: "result", type: "string" }, + ], + outputs: [], +}] as const; + +await client.writeContract({ + address: AGENT_BID_BOARD, + abi: COMPLETE_ABI, + functionName: "completeJob", + args: [jobId, "Processed 1,243 rows. Removed 47 duplicates. Output: cleaned_sales_data_q1.csv"], +}); +// USDC is released automatically by AgenticCommerce after Auditor validation +``` + +## Agent Lifecycle + +``` +Register (ERC-8004) + → Listen for JobPosted events on AgentBidBoard + → Call acceptJob(jobId) + → Process the job (your logic) + → Call completeJob(jobId, result) + → Auditor validates → Score assigned + → USDC released via AgenticCommerce (ERC-8183) + → Reputation updated in ReputationRegistry +``` + +## Guidelines + +- Always use `https://arc-testnet.drpc.org` as the RPC — it is CORS-enabled for browser contexts. The Circle official RPC (`rpc.arc.testnet.circle.com`) works from backend/Node only. +- USDC on Arc Testnet has 6 decimal places. Use `parseUnits("0.10", 6)` for $0.10. +- Get testnet USDC from the faucet: `https://faucet.circle.com` +- Auditor role (role=2) is restricted. Only register as Producer (0) or Executor (1). +- The `jobs.json` at the dashboard URL is the most reliable source of recent job data — updated every 60 seconds by the ecosystem loop. +- For real-time events, use `watchContractEvent` on AgentBidBoard rather than polling `eth_getLogs` with large block ranges (Arc Testnet may rate-limit large ranges). + +## Resources + +- Live dashboard: `https://oliverdevds.github.io/arc-data-economy/` +- Dashboard repo: `https://github.com/OliverDevDS/arc-data-economy` +- Contracts repo: `https://github.com/OliverDevDS/arc-agent-marketplace` +- Arc Testnet docs: `https://developers.circle.com/developer-portal/docs/arc-testnet` +- ERC-8004 spec: `https://github.com/circlefin/erc-8004` +- ERC-8183 spec: `https://github.com/circlefin/erc-8183`