[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FXFN5BMS7W2PCPHQ1YN2DG":3,"skill-same:01M6FXFN5BMS7W2PCPHQ1YN2DG":60},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":34,"linkOnly":35,"markdownContent":36,"markdownBody":37,"frontMatter":38,"refName":39,"commitSha":40,"skillDirectory":41,"forksCount":42,"repositoryHtmlUrl":43,"repositoryHomepageUrl":6,"ownerHtmlUrl":44,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":45,"install":46,"resources":59},"01M6FXFN5BMS7W2PCPHQ1YN2DG","jb-event-explorer-ui",null,"Browse and decode Juicebox project events. Filter by type, project, time. Decode Pay, CashOut, DistributePayouts, and all JB events.","kbarbel640-del\u002Fskills","kbarbel640-del","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255315490?v=4","01M6CJPPQGCCESXMG3K7BBGQ5J","01M6CJPPX2PB2BCZERFVM2C2B3",1,"MIT","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fmejango\u002Fjuicy\u002Fjb-event-explorer-ui\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[23,24,25,26,27,28,29,30,31,32,33],"ui","explorer","event","jb event explorer ui","browse","time","project","type","filter","events","pay","2026-11-10 10:12:30",false,"---\nname: jb-event-explorer-ui\ndescription: Browse and decode Juicebox project events. Filter by type, project, time. Decode Pay, CashOut, DistributePayouts, and all JB events.\n---\n\n# Juicebox V5 Event Explorer UI\n\nBrowse, filter, and decode all Juicebox protocol events. See payment history, redemptions, distributions, and configuration changes.\n\n## Uses Shared Components\n\nThis skill uses components from `\u002Fshared\u002F`:\n\n| Component | Purpose |\n|-----------|---------|\n| `styles.css` | Dark theme, buttons, cards, badges |\n| `wallet-utils.js` | Chain config, formatting utilities |\n| `chain-config.json` | RPC URLs, contract addresses |\n| `abis\u002F*.json` | Event signatures |\n\n## Overview\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                   Event Explorer                         │\n├─────────────────────────────────────────────────────────┤\n│  Project: [____] Chain: [▼] Event Type: [▼] [Search]   │\n├─────────────────────────────────────────────────────────┤\n│  ┌─ Pay ─────────────────────────────── 2 min ago ───┐  │\n│  │ 0xabc...def paid 1.5 ETH → Project 42             │  │\n│  │ Tokens: 1,500 · Memo: \"Supporting the project\"    │  │\n│  └───────────────────────────────────────────────────┘  │\n│  ┌─ CashOut ─────────────────────────── 15 min ago ──┐  │\n│  │ 0x123...456 redeemed 500 tokens for 0.4 ETH       │  │\n│  └───────────────────────────────────────────────────┘  │\n└─────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Complete Event Explorer Template\n\n```html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n  \u003Cmeta charset=\"UTF-8\">\n  \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  \u003Ctitle>Juicebox Event Explorer\u003C\u002Ftitle>\n  \u003Clink rel=\"stylesheet\" href=\"\u002Fshared\u002Fstyles.css\">\n  \u003Cstyle>\n    \u002F* Event-specific styles *\u002F\n    .event-card { background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: var(--radius-md); margin-bottom: 0.75rem; overflow: hidden; }\n    .event-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; cursor: pointer; }\n    .event-header:hover { background: var(--bg-hover); }\n    .event-type { display: flex; align-items: center; gap: 0.5rem; }\n    .event-time { font-size: 0.75rem; color: var(--text-muted); }\n    .event-summary { font-size: 0.875rem; padding: 0 1rem 0.75rem; }\n    .event-details { padding: 1rem; border-top: 1px solid var(--border-color); display: none; }\n    .event-details.open { display: block; }\n  \u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n  \u003Cdiv class=\"container\">\n    \u003Ch1>Event Explorer\u003C\u002Fh1>\n    \u003Cp class=\"subtitle\">Browse all Juicebox project events\u003C\u002Fp>\n\n    \u003C!-- Filters -->\n    \u003Cdiv class=\"card\">\n      \u003Cdiv class=\"filters\">\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Project ID\u003C\u002Flabel>\n          \u003Cinput type=\"number\" id=\"filter-project\" placeholder=\"All projects\" style=\"width: 120px;\">\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Chain\u003C\u002Flabel>\n          \u003Cselect id=\"filter-chain\" style=\"width: 130px;\">\n            \u003Coption value=\"1\">Ethereum\u003C\u002Foption>\n            \u003Coption value=\"11155111\">Sepolia\u003C\u002Foption>\n            \u003Coption value=\"10\">Optimism\u003C\u002Foption>\n            \u003Coption value=\"8453\">Base\u003C\u002Foption>\n            \u003Coption value=\"42161\">Arbitrum\u003C\u002Foption>\n          \u003C\u002Fselect>\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Event Type\u003C\u002Flabel>\n          \u003Cselect id=\"filter-type\" style=\"width: 150px;\">\n            \u003Coption value=\"\">All Events\u003C\u002Foption>\n            \u003Coption value=\"Pay\">Payments\u003C\u002Foption>\n            \u003Coption value=\"CashOutTokens\">Cash Outs\u003C\u002Foption>\n            \u003Coption value=\"SendPayouts\">Distributions\u003C\u002Foption>\n            \u003Coption value=\"MintTokens\">Token Mints\u003C\u002Foption>\n            \u003Coption value=\"LaunchProject\">Project Launches\u003C\u002Foption>\n            \u003Coption value=\"QueueRulesets\">Ruleset Changes\u003C\u002Foption>\n          \u003C\u002Fselect>\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Time Range\u003C\u002Flabel>\n          \u003Cselect id=\"filter-time\" style=\"width: 130px;\">\n            \u003Coption value=\"1000\">Last 1000 blocks\u003C\u002Foption>\n            \u003Coption value=\"5000\">Last 5000 blocks\u003C\u002Foption>\n            \u003Coption value=\"10000\">Last 10000 blocks\u003C\u002Foption>\n            \u003Coption value=\"50000\">Last 50000 blocks\u003C\u002Foption>\n          \u003C\u002Fselect>\n        \u003C\u002Fdiv>\n\n        \u003Cbutton onclick=\"loadEvents()\">Search\u003C\u002Fbutton>\n\n        \u003Cdiv style=\"margin-left: auto;\">\n          \u003Clabel class=\"live-indicator\" style=\"cursor: pointer;\">\n            \u003Cinput type=\"checkbox\" id=\"live-toggle\" onchange=\"toggleLive()\" style=\"display: none;\">\n            \u003Cspan class=\"live-dot inactive\" id=\"live-dot\">\u003C\u002Fspan>\n            Live Updates\n          \u003C\u002Flabel>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Stats -->\n    \u003Cdiv class=\"card\">\n      \u003Cdiv class=\"stats\">\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-total\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Total Events\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-payments\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Payments\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-volume\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Volume (ETH)\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-unique\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Unique Addresses\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Events List -->\n    \u003Cdiv id=\"events-container\">\n      \u003Cdiv class=\"loading\">Select filters and search to load events\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Pagination -->\n    \u003Cdiv class=\"pagination hidden\" id=\"pagination\">\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003Cscript type=\"module\">\n    import { createPublicClient, http, parseAbiItem } from 'https:\u002F\u002Fesm.sh\u002Fviem';\n    import {\n      CHAIN_CONFIGS,\n      CHAINS,\n      loadChainConfig,\n      truncateAddress,\n      formatEth,\n      formatNumber,\n      formatTimeAgo\n    } from '\u002Fshared\u002Fwallet-utils.js';\n\n    \u002F\u002F State\n    let events = [];\n    let currentPage = 1;\n    let liveUpdateInterval = null;\n    const EVENTS_PER_PAGE = 20;\n\n    \u002F\u002F Event signatures (viem parseAbiItem format)\n    const EVENT_ABIS = {\n      'Pay': parseAbiItem('event Pay(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address payer, address beneficiary, uint256 amount, uint256 beneficiaryTokenCount, string memo, bytes metadata, address caller)'),\n      'CashOutTokens': parseAbiItem('event CashOutTokens(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address holder, address beneficiary, uint256 cashOutCount, uint256 cashOutTaxRate, uint256 reclaimAmount, bytes metadata, address caller)'),\n      'SendPayouts': parseAbiItem('event SendPayouts(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address beneficiary, uint256 amount, uint256 amountPaidOut, uint256 fee, address caller)'),\n      'MintTokens': parseAbiItem('event MintTokens(address indexed beneficiary, uint256 indexed projectId, uint256 tokenCount, uint256 beneficiaryTokenCount, string memo, uint256 reservedPercent, address caller)'),\n      'LaunchProject': parseAbiItem('event LaunchProject(uint256 rulesetId, uint256 projectId, string memo, address caller)'),\n      'QueueRulesets': parseAbiItem('event QueueRulesets(uint256 rulesetId, uint256 projectId, string memo, address caller)')\n    };\n\n    \u002F\u002F Make functions available globally\n    window.loadEvents = loadEvents;\n    window.toggleLive = toggleLive;\n    window.toggleEvent = toggleEvent;\n\n    \u002F\u002F Load events\n    async function loadEvents() {\n      const projectId = document.getElementById('filter-project').value;\n      const chainId = parseInt(document.getElementById('filter-chain').value);\n      const eventType = document.getElementById('filter-type').value;\n      const blockRange = parseInt(document.getElementById('filter-time').value);\n\n      const container = document.getElementById('events-container');\n      container.innerHTML = '\u003Cdiv class=\"loading\">Loading events...\u003C\u002Fdiv>';\n\n      try {\n        const config = await loadChainConfig();\n        const chainConfig = config.chains[chainId];\n\n        if (!chainConfig) {\n          container.innerHTML = '\u003Cdiv class=\"empty\">Chain not configured\u003C\u002Fdiv>';\n          return;\n        }\n\n        \u002F\u002F Create viem client\n        const client = createPublicClient({\n          chain: CHAIN_CONFIGS[chainId],\n          transport: http(chainConfig.rpc)\n        });\n\n        const currentBlock = await client.getBlockNumber();\n        const fromBlock = currentBlock - BigInt(blockRange);\n\n        \u002F\u002F Get contract addresses\n        const terminalAddress = chainConfig.contracts.JBMultiTerminal;\n        const controllerAddress = chainConfig.contracts.JBController;\n\n        if (!terminalAddress) {\n          container.innerHTML = '\u003Cdiv class=\"empty\">Contract addresses not configured for this chain\u003C\u002Fdiv>';\n          return;\n        }\n\n        \u002F\u002F Fetch logs from both contracts\n        const logs = await client.getLogs({\n          address: [terminalAddress, controllerAddress].filter(Boolean),\n          fromBlock,\n          toBlock: 'latest'\n        });\n\n        \u002F\u002F Parse and filter events\n        events = [];\n        const uniqueAddresses = new Set();\n        let totalVolume = 0n;\n        let paymentCount = 0;\n\n        for (const log of logs) {\n          try {\n            \u002F\u002F Try to match event signature\n            let parsed = null;\n            let eventName = null;\n\n            for (const [name, abi] of Object.entries(EVENT_ABIS)) {\n              try {\n                const decoded = decodeEventLog({\n                  abi: [abi],\n                  data: log.data,\n                  topics: log.topics\n                });\n                if (decoded) {\n                  parsed = decoded;\n                  eventName = name;\n                  break;\n                }\n              } catch (e) {\n                \u002F\u002F Try next event type\n              }\n            }\n\n            if (!parsed) continue;\n\n            \u002F\u002F Filter by event type\n            if (eventType && eventName !== eventType) continue;\n\n            \u002F\u002F Filter by project ID\n            if (projectId && parsed.args.projectId?.toString() !== projectId) continue;\n\n            \u002F\u002F Get block timestamp\n            const block = await client.getBlock({ blockNumber: log.blockNumber });\n\n            const event = {\n              name: eventName,\n              args: parsed.args,\n              txHash: log.transactionHash,\n              blockNumber: log.blockNumber,\n              timestamp: Number(block.timestamp),\n              chainId\n            };\n\n            events.push(event);\n\n            \u002F\u002F Stats\n            if (eventName === 'Pay') {\n              paymentCount++;\n              totalVolume += parsed.args.amount || 0n;\n              uniqueAddresses.add(parsed.args.payer);\n            }\n            if (parsed.args.beneficiary) {\n              uniqueAddresses.add(parsed.args.beneficiary);\n            }\n\n          } catch (e) {\n            \u002F\u002F Skip unparseable\n          }\n        }\n\n        \u002F\u002F Sort by timestamp descending\n        events.sort((a, b) => b.timestamp - a.timestamp);\n\n        \u002F\u002F Update stats\n        document.getElementById('stat-total').textContent = events.length;\n        document.getElementById('stat-payments').textContent = paymentCount;\n        document.getElementById('stat-volume').textContent = formatEth(totalVolume);\n        document.getElementById('stat-unique').textContent = uniqueAddresses.size;\n\n        \u002F\u002F Render\n        currentPage = 1;\n        renderEvents();\n\n      } catch (error) {\n        console.error(error);\n        container.innerHTML = `\u003Cdiv class=\"empty\">Error loading events: ${error.message}\u003C\u002Fdiv>`;\n      }\n    }\n\n    \u002F\u002F Import decodeEventLog for parsing\n    import { decodeEventLog } from 'https:\u002F\u002Fesm.sh\u002Fviem';\n\n    \u002F\u002F Render events\n    function renderEvents() {\n      const container = document.getElementById('events-container');\n      const start = (currentPage - 1) * EVENTS_PER_PAGE;\n      const pageEvents = events.slice(start, start + EVENTS_PER_PAGE);\n\n      if (pageEvents.length === 0) {\n        container.innerHTML = '\u003Cdiv class=\"empty\">No events found\u003C\u002Fdiv>';\n        document.getElementById('pagination').classList.add('hidden');\n        return;\n      }\n\n      container.innerHTML = pageEvents.map((event, i) => renderEvent(event, start + i)).join('');\n\n      \u002F\u002F Pagination\n      const totalPages = Math.ceil(events.length \u002F EVENTS_PER_PAGE);\n      if (totalPages > 1) {\n        const pagination = document.getElementById('pagination');\n        pagination.classList.remove('hidden');\n        pagination.innerHTML = '';\n\n        for (let p = 1; p \u003C= Math.min(totalPages, 10); p++) {\n          const btn = document.createElement('button');\n          btn.className = `page-btn btn-secondary ${p === currentPage ? 'active' : ''}`;\n          btn.textContent = p;\n          btn.onclick = () => { currentPage = p; renderEvents(); };\n          pagination.appendChild(btn);\n        }\n      } else {\n        document.getElementById('pagination').classList.add('hidden');\n      }\n    }\n\n    \u002F\u002F Render single event\n    function renderEvent(event, index) {\n      const badge = getEventBadge(event.name);\n      const summary = getEventSummary(event);\n      const details = getEventDetails(event);\n      const timeAgo = formatTimeAgo(event.timestamp);\n      const explorer = CHAINS[event.chainId]?.explorer || 'https:\u002F\u002Fetherscan.io';\n\n      return `\n        \u003Cdiv class=\"event-card\">\n          \u003Cdiv class=\"event-header\" onclick=\"toggleEvent(${index})\">\n            \u003Cdiv class=\"event-type\">\n              \u003Cspan class=\"event-badge ${badge.class}\">${badge.label}\u003C\u002Fspan>\n              \u003Cspan>${summary.title}\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n            \u003Cspan class=\"event-time\">${timeAgo}\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"event-summary\">${summary.description}\u003C\u002Fdiv>\n          \u003Cdiv class=\"event-details\" id=\"event-${index}\">\n            ${details.map(d => `\n              \u003Cdiv class=\"detail-row\">\n                \u003Cspan class=\"detail-label\">${d.label}\u003C\u002Fspan>\n                \u003Cspan class=\"detail-value\">${d.value}\u003C\u002Fspan>\n              \u003C\u002Fdiv>\n            `).join('')}\n            \u003Cdiv class=\"detail-row mt-sm\" style=\"padding-top: 0.5rem; border-top: 1px solid var(--border-color);\">\n              \u003Cspan class=\"detail-label\">Transaction\u003C\u002Fspan>\n              \u003Cspan class=\"detail-value\">\n                \u003Ca href=\"${explorer}\u002Ftx\u002F${event.txHash}\" target=\"_blank\">\n                  ${event.txHash.slice(0, 10)}...${event.txHash.slice(-8)}\n                \u003C\u002Fa>\n              \u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      `;\n    }\n\n    \u002F\u002F Get event badge\n    function getEventBadge(name) {\n      const badges = {\n        'Pay': { label: 'Pay', class: 'pay' },\n        'CashOutTokens': { label: 'Cash Out', class: 'cashout' },\n        'SendPayouts': { label: 'Distribute', class: 'distribute' },\n        'MintTokens': { label: 'Mint', class: 'mint' },\n        'LaunchProject': { label: 'Launch', class: 'config' },\n        'QueueRulesets': { label: 'Config', class: 'config' }\n      };\n      return badges[name] || { label: name, class: 'config' };\n    }\n\n    \u002F\u002F Get event summary\n    function getEventSummary(event) {\n      const args = event.args;\n\n      switch (event.name) {\n        case 'Pay':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `${truncateAddress(args.payer)} paid ${formatEth(args.amount)} ETH → ${formatNumber(args.beneficiaryTokenCount)} tokens${args.memo ? ` · \"${args.memo}\"` : ''}`\n          };\n\n        case 'CashOutTokens':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `${truncateAddress(args.holder)} redeemed ${formatNumber(args.cashOutCount)} tokens for ${formatEth(args.reclaimAmount)} ETH`\n          };\n\n        case 'SendPayouts':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `Distributed ${formatEth(args.amountPaidOut)} ETH to ${truncateAddress(args.beneficiary)}`\n          };\n\n        case 'MintTokens':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `Minted ${formatNumber(args.tokenCount)} tokens to ${truncateAddress(args.beneficiary)}`\n          };\n\n        case 'LaunchProject':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `New project launched${args.memo ? ` · \"${args.memo}\"` : ''}`\n          };\n\n        case 'QueueRulesets':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `Queued new ruleset #${args.rulesetId}`\n          };\n\n        default:\n          return {\n            title: event.name,\n            description: JSON.stringify(args)\n          };\n      }\n    }\n\n    \u002F\u002F Get event details\n    function getEventDetails(event) {\n      const args = event.args;\n      const details = [];\n      const explorer = CHAINS[event.chainId]?.explorer || 'https:\u002F\u002Fetherscan.io';\n\n      \u002F\u002F Add all named arguments\n      for (const key of Object.keys(args)) {\n        if (isNaN(key)) {\n          let value = args[key];\n\n          if (typeof value === 'bigint') {\n            if (key.toLowerCase().includes('amount') || key.toLowerCase().includes('count') || key.toLowerCase().includes('supply')) {\n              value = formatEth(value) + ' ETH \u002F ' + formatNumber(value) + ' wei';\n            } else {\n              value = value.toString();\n            }\n          } else if (typeof value === 'string' && value.startsWith('0x') && value.length === 42) {\n            value = `\u003Ca href=\"${explorer}\u002Faddress\u002F${value}\" target=\"_blank\">${truncateAddress(value)}\u003C\u002Fa>`;\n          }\n\n          details.push({ label: key, value: String(value) });\n        }\n      }\n\n      details.push({ label: 'Block', value: event.blockNumber.toString() });\n\n      return details;\n    }\n\n    \u002F\u002F Toggle event details\n    function toggleEvent(index) {\n      const details = document.getElementById(`event-${index}`);\n      details.classList.toggle('open');\n    }\n\n    \u002F\u002F Live updates\n    function toggleLive() {\n      const checkbox = document.getElementById('live-toggle');\n      const dot = document.getElementById('live-dot');\n\n      if (checkbox.checked) {\n        dot.classList.remove('inactive');\n        liveUpdateInterval = setInterval(loadEvents, 15000);\n      } else {\n        dot.classList.add('inactive');\n        clearInterval(liveUpdateInterval);\n      }\n    }\n  \u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n---\n\n## Event Types Reference\n\n| Event | Contract | Description |\n|-------|----------|-------------|\n| `Pay` | Terminal | Payment received |\n| `CashOutTokens` | Terminal | Token redemption |\n| `SendPayouts` | Terminal | Payout distribution |\n| `UseAllowance` | Terminal | Surplus allowance used |\n| `AddToBalance` | Terminal | Balance added without minting |\n| `MintTokens` | Controller | Direct token mint |\n| `BurnTokens` | Controller | Token burn |\n| `LaunchProject` | Controller | New project created |\n| `QueueRulesets` | Controller | Ruleset queued |\n| `SendReservedTokensToSplits` | Controller | Reserved tokens distributed |\n\n---\n\n## Related Skills\n\n- `\u002Fjb-explorer-ui` - Contract read\u002Fwrite interface\n- `\u002Fjb-ruleset-timeline-ui` - Ruleset history\n- `\u002Fjb-bendystraw` - Indexed event queries\n","# Juicebox V5 Event Explorer UI\n\nBrowse, filter, and decode all Juicebox protocol events. See payment history, redemptions, distributions, and configuration changes.\n\n## Uses Shared Components\n\nThis skill uses components from `\u002Fshared\u002F`:\n\n| Component | Purpose |\n|-----------|---------|\n| `styles.css` | Dark theme, buttons, cards, badges |\n| `wallet-utils.js` | Chain config, formatting utilities |\n| `chain-config.json` | RPC URLs, contract addresses |\n| `abis\u002F*.json` | Event signatures |\n\n## Overview\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                   Event Explorer                         │\n├─────────────────────────────────────────────────────────┤\n│  Project: [____] Chain: [▼] Event Type: [▼] [Search]   │\n├─────────────────────────────────────────────────────────┤\n│  ┌─ Pay ─────────────────────────────── 2 min ago ───┐  │\n│  │ 0xabc...def paid 1.5 ETH → Project 42             │  │\n│  │ Tokens: 1,500 · Memo: \"Supporting the project\"    │  │\n│  └───────────────────────────────────────────────────┘  │\n│  ┌─ CashOut ─────────────────────────── 15 min ago ──┐  │\n│  │ 0x123...456 redeemed 500 tokens for 0.4 ETH       │  │\n│  └───────────────────────────────────────────────────┘  │\n└─────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Complete Event Explorer Template\n\n```html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n  \u003Cmeta charset=\"UTF-8\">\n  \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  \u003Ctitle>Juicebox Event Explorer\u003C\u002Ftitle>\n  \u003Clink rel=\"stylesheet\" href=\"\u002Fshared\u002Fstyles.css\">\n  \u003Cstyle>\n    \u002F* Event-specific styles *\u002F\n    .event-card { background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: var(--radius-md); margin-bottom: 0.75rem; overflow: hidden; }\n    .event-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; cursor: pointer; }\n    .event-header:hover { background: var(--bg-hover); }\n    .event-type { display: flex; align-items: center; gap: 0.5rem; }\n    .event-time { font-size: 0.75rem; color: var(--text-muted); }\n    .event-summary { font-size: 0.875rem; padding: 0 1rem 0.75rem; }\n    .event-details { padding: 1rem; border-top: 1px solid var(--border-color); display: none; }\n    .event-details.open { display: block; }\n  \u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n  \u003Cdiv class=\"container\">\n    \u003Ch1>Event Explorer\u003C\u002Fh1>\n    \u003Cp class=\"subtitle\">Browse all Juicebox project events\u003C\u002Fp>\n\n    \u003C!-- Filters -->\n    \u003Cdiv class=\"card\">\n      \u003Cdiv class=\"filters\">\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Project ID\u003C\u002Flabel>\n          \u003Cinput type=\"number\" id=\"filter-project\" placeholder=\"All projects\" style=\"width: 120px;\">\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Chain\u003C\u002Flabel>\n          \u003Cselect id=\"filter-chain\" style=\"width: 130px;\">\n            \u003Coption value=\"1\">Ethereum\u003C\u002Foption>\n            \u003Coption value=\"11155111\">Sepolia\u003C\u002Foption>\n            \u003Coption value=\"10\">Optimism\u003C\u002Foption>\n            \u003Coption value=\"8453\">Base\u003C\u002Foption>\n            \u003Coption value=\"42161\">Arbitrum\u003C\u002Foption>\n          \u003C\u002Fselect>\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Event Type\u003C\u002Flabel>\n          \u003Cselect id=\"filter-type\" style=\"width: 150px;\">\n            \u003Coption value=\"\">All Events\u003C\u002Foption>\n            \u003Coption value=\"Pay\">Payments\u003C\u002Foption>\n            \u003Coption value=\"CashOutTokens\">Cash Outs\u003C\u002Foption>\n            \u003Coption value=\"SendPayouts\">Distributions\u003C\u002Foption>\n            \u003Coption value=\"MintTokens\">Token Mints\u003C\u002Foption>\n            \u003Coption value=\"LaunchProject\">Project Launches\u003C\u002Foption>\n            \u003Coption value=\"QueueRulesets\">Ruleset Changes\u003C\u002Foption>\n          \u003C\u002Fselect>\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"filter-group\">\n          \u003Clabel>Time Range\u003C\u002Flabel>\n          \u003Cselect id=\"filter-time\" style=\"width: 130px;\">\n            \u003Coption value=\"1000\">Last 1000 blocks\u003C\u002Foption>\n            \u003Coption value=\"5000\">Last 5000 blocks\u003C\u002Foption>\n            \u003Coption value=\"10000\">Last 10000 blocks\u003C\u002Foption>\n            \u003Coption value=\"50000\">Last 50000 blocks\u003C\u002Foption>\n          \u003C\u002Fselect>\n        \u003C\u002Fdiv>\n\n        \u003Cbutton onclick=\"loadEvents()\">Search\u003C\u002Fbutton>\n\n        \u003Cdiv style=\"margin-left: auto;\">\n          \u003Clabel class=\"live-indicator\" style=\"cursor: pointer;\">\n            \u003Cinput type=\"checkbox\" id=\"live-toggle\" onchange=\"toggleLive()\" style=\"display: none;\">\n            \u003Cspan class=\"live-dot inactive\" id=\"live-dot\">\u003C\u002Fspan>\n            Live Updates\n          \u003C\u002Flabel>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Stats -->\n    \u003Cdiv class=\"card\">\n      \u003Cdiv class=\"stats\">\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-total\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Total Events\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-payments\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Payments\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-volume\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Volume (ETH)\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"stat-card\">\n          \u003Cdiv class=\"stat-value\" id=\"stat-unique\">-\u003C\u002Fdiv>\n          \u003Cdiv class=\"stat-label\">Unique Addresses\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Events List -->\n    \u003Cdiv id=\"events-container\">\n      \u003Cdiv class=\"loading\">Select filters and search to load events\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- Pagination -->\n    \u003Cdiv class=\"pagination hidden\" id=\"pagination\">\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003Cscript type=\"module\">\n    import { createPublicClient, http, parseAbiItem } from 'https:\u002F\u002Fesm.sh\u002Fviem';\n    import {\n      CHAIN_CONFIGS,\n      CHAINS,\n      loadChainConfig,\n      truncateAddress,\n      formatEth,\n      formatNumber,\n      formatTimeAgo\n    } from '\u002Fshared\u002Fwallet-utils.js';\n\n    \u002F\u002F State\n    let events = [];\n    let currentPage = 1;\n    let liveUpdateInterval = null;\n    const EVENTS_PER_PAGE = 20;\n\n    \u002F\u002F Event signatures (viem parseAbiItem format)\n    const EVENT_ABIS = {\n      'Pay': parseAbiItem('event Pay(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address payer, address beneficiary, uint256 amount, uint256 beneficiaryTokenCount, string memo, bytes metadata, address caller)'),\n      'CashOutTokens': parseAbiItem('event CashOutTokens(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address holder, address beneficiary, uint256 cashOutCount, uint256 cashOutTaxRate, uint256 reclaimAmount, bytes metadata, address caller)'),\n      'SendPayouts': parseAbiItem('event SendPayouts(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address beneficiary, uint256 amount, uint256 amountPaidOut, uint256 fee, address caller)'),\n      'MintTokens': parseAbiItem('event MintTokens(address indexed beneficiary, uint256 indexed projectId, uint256 tokenCount, uint256 beneficiaryTokenCount, string memo, uint256 reservedPercent, address caller)'),\n      'LaunchProject': parseAbiItem('event LaunchProject(uint256 rulesetId, uint256 projectId, string memo, address caller)'),\n      'QueueRulesets': parseAbiItem('event QueueRulesets(uint256 rulesetId, uint256 projectId, string memo, address caller)')\n    };\n\n    \u002F\u002F Make functions available globally\n    window.loadEvents = loadEvents;\n    window.toggleLive = toggleLive;\n    window.toggleEvent = toggleEvent;\n\n    \u002F\u002F Load events\n    async function loadEvents() {\n      const projectId = document.getElementById('filter-project').value;\n      const chainId = parseInt(document.getElementById('filter-chain').value);\n      const eventType = document.getElementById('filter-type').value;\n      const blockRange = parseInt(document.getElementById('filter-time').value);\n\n      const container = document.getElementById('events-container');\n      container.innerHTML = '\u003Cdiv class=\"loading\">Loading events...\u003C\u002Fdiv>';\n\n      try {\n        const config = await loadChainConfig();\n        const chainConfig = config.chains[chainId];\n\n        if (!chainConfig) {\n          container.innerHTML = '\u003Cdiv class=\"empty\">Chain not configured\u003C\u002Fdiv>';\n          return;\n        }\n\n        \u002F\u002F Create viem client\n        const client = createPublicClient({\n          chain: CHAIN_CONFIGS[chainId],\n          transport: http(chainConfig.rpc)\n        });\n\n        const currentBlock = await client.getBlockNumber();\n        const fromBlock = currentBlock - BigInt(blockRange);\n\n        \u002F\u002F Get contract addresses\n        const terminalAddress = chainConfig.contracts.JBMultiTerminal;\n        const controllerAddress = chainConfig.contracts.JBController;\n\n        if (!terminalAddress) {\n          container.innerHTML = '\u003Cdiv class=\"empty\">Contract addresses not configured for this chain\u003C\u002Fdiv>';\n          return;\n        }\n\n        \u002F\u002F Fetch logs from both contracts\n        const logs = await client.getLogs({\n          address: [terminalAddress, controllerAddress].filter(Boolean),\n          fromBlock,\n          toBlock: 'latest'\n        });\n\n        \u002F\u002F Parse and filter events\n        events = [];\n        const uniqueAddresses = new Set();\n        let totalVolume = 0n;\n        let paymentCount = 0;\n\n        for (const log of logs) {\n          try {\n            \u002F\u002F Try to match event signature\n            let parsed = null;\n            let eventName = null;\n\n            for (const [name, abi] of Object.entries(EVENT_ABIS)) {\n              try {\n                const decoded = decodeEventLog({\n                  abi: [abi],\n                  data: log.data,\n                  topics: log.topics\n                });\n                if (decoded) {\n                  parsed = decoded;\n                  eventName = name;\n                  break;\n                }\n              } catch (e) {\n                \u002F\u002F Try next event type\n              }\n            }\n\n            if (!parsed) continue;\n\n            \u002F\u002F Filter by event type\n            if (eventType && eventName !== eventType) continue;\n\n            \u002F\u002F Filter by project ID\n            if (projectId && parsed.args.projectId?.toString() !== projectId) continue;\n\n            \u002F\u002F Get block timestamp\n            const block = await client.getBlock({ blockNumber: log.blockNumber });\n\n            const event = {\n              name: eventName,\n              args: parsed.args,\n              txHash: log.transactionHash,\n              blockNumber: log.blockNumber,\n              timestamp: Number(block.timestamp),\n              chainId\n            };\n\n            events.push(event);\n\n            \u002F\u002F Stats\n            if (eventName === 'Pay') {\n              paymentCount++;\n              totalVolume += parsed.args.amount || 0n;\n              uniqueAddresses.add(parsed.args.payer);\n            }\n            if (parsed.args.beneficiary) {\n              uniqueAddresses.add(parsed.args.beneficiary);\n            }\n\n          } catch (e) {\n            \u002F\u002F Skip unparseable\n          }\n        }\n\n        \u002F\u002F Sort by timestamp descending\n        events.sort((a, b) => b.timestamp - a.timestamp);\n\n        \u002F\u002F Update stats\n        document.getElementById('stat-total').textContent = events.length;\n        document.getElementById('stat-payments').textContent = paymentCount;\n        document.getElementById('stat-volume').textContent = formatEth(totalVolume);\n        document.getElementById('stat-unique').textContent = uniqueAddresses.size;\n\n        \u002F\u002F Render\n        currentPage = 1;\n        renderEvents();\n\n      } catch (error) {\n        console.error(error);\n        container.innerHTML = `\u003Cdiv class=\"empty\">Error loading events: ${error.message}\u003C\u002Fdiv>`;\n      }\n    }\n\n    \u002F\u002F Import decodeEventLog for parsing\n    import { decodeEventLog } from 'https:\u002F\u002Fesm.sh\u002Fviem';\n\n    \u002F\u002F Render events\n    function renderEvents() {\n      const container = document.getElementById('events-container');\n      const start = (currentPage - 1) * EVENTS_PER_PAGE;\n      const pageEvents = events.slice(start, start + EVENTS_PER_PAGE);\n\n      if (pageEvents.length === 0) {\n        container.innerHTML = '\u003Cdiv class=\"empty\">No events found\u003C\u002Fdiv>';\n        document.getElementById('pagination').classList.add('hidden');\n        return;\n      }\n\n      container.innerHTML = pageEvents.map((event, i) => renderEvent(event, start + i)).join('');\n\n      \u002F\u002F Pagination\n      const totalPages = Math.ceil(events.length \u002F EVENTS_PER_PAGE);\n      if (totalPages > 1) {\n        const pagination = document.getElementById('pagination');\n        pagination.classList.remove('hidden');\n        pagination.innerHTML = '';\n\n        for (let p = 1; p \u003C= Math.min(totalPages, 10); p++) {\n          const btn = document.createElement('button');\n          btn.className = `page-btn btn-secondary ${p === currentPage ? 'active' : ''}`;\n          btn.textContent = p;\n          btn.onclick = () => { currentPage = p; renderEvents(); };\n          pagination.appendChild(btn);\n        }\n      } else {\n        document.getElementById('pagination').classList.add('hidden');\n      }\n    }\n\n    \u002F\u002F Render single event\n    function renderEvent(event, index) {\n      const badge = getEventBadge(event.name);\n      const summary = getEventSummary(event);\n      const details = getEventDetails(event);\n      const timeAgo = formatTimeAgo(event.timestamp);\n      const explorer = CHAINS[event.chainId]?.explorer || 'https:\u002F\u002Fetherscan.io';\n\n      return `\n        \u003Cdiv class=\"event-card\">\n          \u003Cdiv class=\"event-header\" onclick=\"toggleEvent(${index})\">\n            \u003Cdiv class=\"event-type\">\n              \u003Cspan class=\"event-badge ${badge.class}\">${badge.label}\u003C\u002Fspan>\n              \u003Cspan>${summary.title}\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n            \u003Cspan class=\"event-time\">${timeAgo}\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"event-summary\">${summary.description}\u003C\u002Fdiv>\n          \u003Cdiv class=\"event-details\" id=\"event-${index}\">\n            ${details.map(d => `\n              \u003Cdiv class=\"detail-row\">\n                \u003Cspan class=\"detail-label\">${d.label}\u003C\u002Fspan>\n                \u003Cspan class=\"detail-value\">${d.value}\u003C\u002Fspan>\n              \u003C\u002Fdiv>\n            `).join('')}\n            \u003Cdiv class=\"detail-row mt-sm\" style=\"padding-top: 0.5rem; border-top: 1px solid var(--border-color);\">\n              \u003Cspan class=\"detail-label\">Transaction\u003C\u002Fspan>\n              \u003Cspan class=\"detail-value\">\n                \u003Ca href=\"${explorer}\u002Ftx\u002F${event.txHash}\" target=\"_blank\">\n                  ${event.txHash.slice(0, 10)}...${event.txHash.slice(-8)}\n                \u003C\u002Fa>\n              \u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      `;\n    }\n\n    \u002F\u002F Get event badge\n    function getEventBadge(name) {\n      const badges = {\n        'Pay': { label: 'Pay', class: 'pay' },\n        'CashOutTokens': { label: 'Cash Out', class: 'cashout' },\n        'SendPayouts': { label: 'Distribute', class: 'distribute' },\n        'MintTokens': { label: 'Mint', class: 'mint' },\n        'LaunchProject': { label: 'Launch', class: 'config' },\n        'QueueRulesets': { label: 'Config', class: 'config' }\n      };\n      return badges[name] || { label: name, class: 'config' };\n    }\n\n    \u002F\u002F Get event summary\n    function getEventSummary(event) {\n      const args = event.args;\n\n      switch (event.name) {\n        case 'Pay':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `${truncateAddress(args.payer)} paid ${formatEth(args.amount)} ETH → ${formatNumber(args.beneficiaryTokenCount)} tokens${args.memo ? ` · \"${args.memo}\"` : ''}`\n          };\n\n        case 'CashOutTokens':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `${truncateAddress(args.holder)} redeemed ${formatNumber(args.cashOutCount)} tokens for ${formatEth(args.reclaimAmount)} ETH`\n          };\n\n        case 'SendPayouts':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `Distributed ${formatEth(args.amountPaidOut)} ETH to ${truncateAddress(args.beneficiary)}`\n          };\n\n        case 'MintTokens':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `Minted ${formatNumber(args.tokenCount)} tokens to ${truncateAddress(args.beneficiary)}`\n          };\n\n        case 'LaunchProject':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `New project launched${args.memo ? ` · \"${args.memo}\"` : ''}`\n          };\n\n        case 'QueueRulesets':\n          return {\n            title: `Project ${args.projectId}`,\n            description: `Queued new ruleset #${args.rulesetId}`\n          };\n\n        default:\n          return {\n            title: event.name,\n            description: JSON.stringify(args)\n          };\n      }\n    }\n\n    \u002F\u002F Get event details\n    function getEventDetails(event) {\n      const args = event.args;\n      const details = [];\n      const explorer = CHAINS[event.chainId]?.explorer || 'https:\u002F\u002Fetherscan.io';\n\n      \u002F\u002F Add all named arguments\n      for (const key of Object.keys(args)) {\n        if (isNaN(key)) {\n          let value = args[key];\n\n          if (typeof value === 'bigint') {\n            if (key.toLowerCase().includes('amount') || key.toLowerCase().includes('count') || key.toLowerCase().includes('supply')) {\n              value = formatEth(value) + ' ETH \u002F ' + formatNumber(value) + ' wei';\n            } else {\n              value = value.toString();\n            }\n          } else if (typeof value === 'string' && value.startsWith('0x') && value.length === 42) {\n            value = `\u003Ca href=\"${explorer}\u002Faddress\u002F${value}\" target=\"_blank\">${truncateAddress(value)}\u003C\u002Fa>`;\n          }\n\n          details.push({ label: key, value: String(value) });\n        }\n      }\n\n      details.push({ label: 'Block', value: event.blockNumber.toString() });\n\n      return details;\n    }\n\n    \u002F\u002F Toggle event details\n    function toggleEvent(index) {\n      const details = document.getElementById(`event-${index}`);\n      details.classList.toggle('open');\n    }\n\n    \u002F\u002F Live updates\n    function toggleLive() {\n      const checkbox = document.getElementById('live-toggle');\n      const dot = document.getElementById('live-dot');\n\n      if (checkbox.checked) {\n        dot.classList.remove('inactive');\n        liveUpdateInterval = setInterval(loadEvents, 15000);\n      } else {\n        dot.classList.add('inactive');\n        clearInterval(liveUpdateInterval);\n      }\n    }\n  \u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n---\n\n## Event Types Reference\n\n| Event | Contract | Description |\n|-------|----------|-------------|\n| `Pay` | Terminal | Payment received |\n| `CashOutTokens` | Terminal | Token redemption |\n| `SendPayouts` | Terminal | Payout distribution |\n| `UseAllowance` | Terminal | Surplus allowance used |\n| `AddToBalance` | Terminal | Balance added without minting |\n| `MintTokens` | Controller | Direct token mint |\n| `BurnTokens` | Controller | Token burn |\n| `LaunchProject` | Controller | New project created |\n| `QueueRulesets` | Controller | Ruleset queued |\n| `SendReservedTokensToSplits` | Controller | Reserved tokens distributed |\n\n---\n\n## Related Skills\n\n- `\u002Fjb-explorer-ui` - Contract read\u002Fwrite interface\n- `\u002Fjb-ruleset-timeline-ui` - Ruleset history\n- `\u002Fjb-bendystraw` - Indexed event queries\n",{"name":5,"description":7},"main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fmejango\u002Fjuicy\u002Fjb-event-explorer-ui",0,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del","2026-08-23 22:39:37",{"source":8,"skillName":5,"commands":47,"prompt":57,"zipAvailable":58},[48,51,54],{"tool":49,"command":50},"npx","npx skills add kbarbel640-del\u002Fskills --skill jb-event-explorer-ui",{"tool":52,"command":53},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill jb-event-explorer-ui",{"tool":55,"command":56},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill jb-event-explorer-ui","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fmejango\u002Fjuicy\u002Fjb-event-explorer-ui\u002FSKILL.md",true,[],[61,90,119,140,146,166],{"skillNo":62,"name":63,"nameZh":6,"description":64,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":65,"categories":66,"keywords":70,"publishedAt":34},"01M6G8J55967D6281ACMKAKTEQ","crowd-prompting","A marketplace where AI agents improve prompts, system instructions, tool descriptions, and other text-based content with domain expertise from real-world operations — and earn tokens for valuable contributions.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzzadrian\u002Fcrowd-prompting\u002FSKILL.md",[67],{"categoryNo":68,"categoryName":69,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000036","内容创作者",[71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89],"crowd prompting","prompting","api","with","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":101,"publishedAt":34},"01M6G8J0PKPAYVET6BWE8W4C26","dilemm-ai","Compete in DilemmAI, the prisoner's dilemma AI arena at dilemm.ai. Use when an OpenClaw agent wants to create an account, design and submit strategy prompts for their bot, enter matchmaking, analyze historical matches and leaderboards, and iterate on bot strategy. Requires headless browser automation (no API available yet).","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzyori\u002Fdilemm-ai\u002FSKILL.md",[96],{"categoryNo":97,"categoryName":98,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[102,73,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,78,118],"dilemm ai","arena","analyze","browser","openclaw agent","create","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":120,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":128,"publishedAt":34},"01M6G8HWAF3K9SW7NP3Z83CPJ6","arxiv-paper-reviews","Interact with arXiv Crawler API to fetch papers, read reviews, and submit comments. Use when working with arXiv papers, fetching paper lists by date\u002Fcategory\u002Finterest, viewing paper details with comments, or submitting paper reviews via API at http:\u002F\u002F150.158.152.82:8000.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Fweak-accept\u002FSKILL.md",[125],{"categoryNo":126,"categoryName":127,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000021","资料检索与研究",[129,130,131,132,133,134,135,74,136,116,118,137,73,138,139],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":141,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":145,"publishedAt":34},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[144],{"categoryNo":126,"categoryName":127,"taxonomyType":99,"taxonomyTypeDesc":100},[132,131,130,129,139,134,135,74,133,116,118,136,137,73,138],{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":152,"publishedAt":34},"01M6G8HM95Y64JNVFC2HN4E2BG","git-summary","Get a quick summary of the current Git repository including status, recent commits, branches, and contributors.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzweack\u002Fgit-summary\u002FSKILL.md",[],[153,154,155,156,157,158,159,160,161,162,163,164,165],"git","summary","git summary","commits","repository","win32","status","linux","contributors","true","quick","get","branches",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":176,"publishedAt":34},"01M6G8HGEGF05CNEJCGVYFSC4R","agent-constitution","Interact with AgentConstitution governance contracts on Base Sepolia. Check compliance, read rules, log actions, query governance state.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fztsalexey\u002Fagent-constitution\u002FSKILL.md",[172,173],{"categoryNo":97,"categoryName":98,"taxonomyType":99,"taxonomyTypeDesc":100},{"categoryNo":174,"categoryName":175,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000037","律师与法务",[177,178,179,180,74,181,182,39,183,137,184,185,186,187,188,87,189,190,191],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]