skills/bap578-analytics/SKILL.md
Use this skill when defining metrics, building indexers, creating dashboards, or generating reports for BAP-578 Non-Fungible Agents from minting, vault funding, treasury, engagement, and event data.
npx skillsauth add chatandbuild/chatchat-skills BAP-578 Analytics & IndexingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill to define metrics, build indexers, create dashboards, and generate reports for BAP-578 Non-Fungible Agents. This covers metric definitions with data sources, event indexing pipelines, aggregation patterns, dashboard design, alerting strategies, and reporting templates.
The analytics layer turns raw on-chain events and state reads into actionable insights. It answers questions about adoption, engagement, revenue, and ecosystem health. Analytics don't modify the contract — they observe and interpret.
All on-chain events are immutable and permanently available. The analytics layer indexes and stores:
AgentCreated event (minting activity)AgentFunded event (funding flows)AgentWithdraw event (withdrawal patterns)AgentStatusChanged event (lifecycle data)MetadataUpdated event (engagement signals)Transfer event (ownership changes)Every metric maps directly to immutable on-chain data. Events cannot be modified after emission. State reads return the current authoritative values. The analytics pipeline can be independently verified by anyone with RPC access.
Total Agents Minted
getTotalSupply() or count of AgentCreated eventsUnique Owners
owner values from AgentCreated events or tokensOfOwnerActive Agent Ratio
(Active agents / Total agents) × 100active == trueAgent Funding TVL (Total Value Locked)
getAgentState(tokenId).balance for all tokensPaid Mint Count
Total supply - Free mintsisFreeMint(tokenId) == falseTreasury Revenue
Paid mints × Mint feeAgentCreated transactionsMonthly Active Agents (MAA)
Mint Velocity
Mints per day/week/monthAgentCreated events grouped by time periodAverage Agent Balance
TVL / Active agent countFree-to-Paid Conversion Rate
(Users with paid mints / Users with only free mints) × 100Metadata Update Frequency
MetadataUpdated events per weekLogic Contract Adoption
(Agents with non-zero logicAddress / Total agents) × 100logicAddress != address(0)Churn Rate
(Agents deactivated in period / Active agents at period start) × 100AgentStatusChanged events where active == falseAverage Agent Lifetime
Average time between creation and last activityAgentCreated timestamp vs last event timestampAgentCreated(uint256 indexed tokenId, address indexed owner, address logicAddress, string metadataURI)
AgentFunded(uint256 indexed tokenId, address indexed funder, uint256 amount)
AgentWithdraw(uint256 indexed tokenId, address indexed owner, uint256 amount)
AgentStatusChanged(uint256 indexed tokenId, bool active)
MetadataUpdated(uint256 indexed tokenId, string newURI)
Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
From AgentCreated:
msg.value in transaction)From AgentFunded:
From AgentWithdraw:
From AgentStatusChanged:
From MetadataUpdated:
From Transfer:
BNB Chain (RPC)
│
│ poll events / websocket
▼
┌──────────────┐
│ Event Poller │ ← reads AgentCreated, AgentFunded, etc.
└──────┬───────┘
│ parse & normalize
▼
┌──────────────┐
│ Processor │ ← computes derived metrics
└──────┬───────┘
│ store
▼
┌──────────────┐
│ Database │ ← PostgreSQL, SQLite, or in-memory
└──────┬───────┘
│ query
▼
┌──────────────┐
│ API / UI │ ← REST API or dashboard
└──────────────┘
const { ethers } = require("ethers");
class BAP578Indexer {
constructor(provider, contractAddress, abi) {
this.contract = new ethers.Contract(contractAddress, abi, provider);
this.lastBlock = 0;
this.events = [];
}
async indexFromBlock(fromBlock) {
const eventNames = [
"AgentCreated",
"AgentFunded",
"AgentWithdraw",
"AgentStatusChanged",
"MetadataUpdated",
];
for (const name of eventNames) {
const events = await this.contract.queryFilter(
this.contract.filters[name](),
fromBlock,
"latest"
);
for (const event of events) {
const block = await event.getBlock();
this.events.push({
name,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
timestamp: new Date(block.timestamp * 1000),
args: this.parseArgs(name, event.args),
});
}
}
this.events.sort((a, b) => a.blockNumber - b.blockNumber);
this.lastBlock = await this.contract.runner.provider.getBlockNumber();
}
parseArgs(name, args) {
switch (name) {
case "AgentCreated":
return {
tokenId: args.tokenId.toString(),
owner: args.owner,
logicAddress: args.logicAddress,
metadataURI: args.metadataURI,
};
case "AgentFunded":
return {
tokenId: args.tokenId.toString(),
funder: args.funder,
amount: ethers.formatEther(args.amount),
};
case "AgentWithdraw":
return {
tokenId: args.tokenId.toString(),
owner: args.owner,
amount: ethers.formatEther(args.amount),
};
case "AgentStatusChanged":
return {
tokenId: args.tokenId.toString(),
active: args.active,
};
case "MetadataUpdated":
return {
tokenId: args.tokenId.toString(),
newURI: args.newURI,
};
default:
return args;
}
}
computeMetrics() {
const created = this.events.filter((e) => e.name === "AgentCreated");
const funded = this.events.filter((e) => e.name === "AgentFunded");
const withdrawn = this.events.filter((e) => e.name === "AgentWithdraw");
const statusChanged = this.events.filter((e) => e.name === "AgentStatusChanged");
const metaUpdated = this.events.filter((e) => e.name === "MetadataUpdated");
const uniqueOwners = new Set(created.map((e) => e.args.owner)).size;
const totalFunded = funded.reduce(
(sum, e) => sum + parseFloat(e.args.amount), 0
);
const totalWithdrawn = withdrawn.reduce(
(sum, e) => sum + parseFloat(e.args.amount), 0
);
const deactivations = statusChanged.filter((e) => !e.args.active).length;
const reactivations = statusChanged.filter((e) => e.args.active).length;
return {
totalMinted: created.length,
uniqueOwners,
totalFunded: totalFunded.toFixed(4) + " BNB",
totalWithdrawn: totalWithdrawn.toFixed(4) + " BNB",
netFunding: (totalFunded - totalWithdrawn).toFixed(4) + " BNB",
deactivations,
reactivations,
metadataUpdates: metaUpdated.length,
};
}
mintsByPeriod(periodDays = 7) {
const created = this.events.filter((e) => e.name === "AgentCreated");
const periods = {};
for (const event of created) {
const periodStart = new Date(event.timestamp);
periodStart.setDate(
periodStart.getDate() - (periodStart.getDate() % periodDays)
);
const key = periodStart.toISOString().split("T")[0];
periods[key] = (periods[key] || 0) + 1;
}
return periods;
}
}
function startRealTimeMonitoring(contract) {
contract.on("AgentCreated", (tokenId, owner, logicAddress, metadataURI, event) => {
console.log(`[MINT] Agent #${tokenId} by ${owner} at block ${event.log.blockNumber}`);
// Update metrics, send to dashboard, trigger alerts
});
contract.on("AgentFunded", (tokenId, funder, amount, event) => {
console.log(`[FUND] Agent #${tokenId} +${ethers.formatEther(amount)} BNB`);
});
contract.on("AgentWithdraw", (tokenId, owner, amount, event) => {
console.log(`[WITHDRAW] Agent #${tokenId} -${ethers.formatEther(amount)} BNB`);
});
contract.on("AgentStatusChanged", (tokenId, active) => {
console.log(`[STATUS] Agent #${tokenId} → ${active ? "active" : "inactive"}`);
});
console.log("Real-time monitoring started...");
}
┌─────────────────────────────────────────────┐
│ BAP-578 Analytics Dashboard │
├──────────┬──────────┬──────────┬────────────┤
│ Total │ Active │ TVL │ Treasury │
│ Minted │ Agents │ │ Balance │
│ 1,247 │ 892 │ 45.3 BNB │ 12.1 BNB │
├──────────┴──────────┴──────────┴────────────┤
│ Minting Activity (line chart — last 30d) │
│ ▁▂▃▅▇▆▄▃▂▅▇▆▅▄▃▂▁▂▃▅▇▆▅▄▃▂▁ │
├─────────────────────┬───────────────────────┤
│ Top Owners (table) │ Funding Flow (chart) │
│ 0xABC: 15 agents │ In: +12.5 BNB │
│ 0xDEF: 12 agents │ Out: -3.2 BNB │
│ 0x123: 8 agents │ Net: +9.3 BNB │
├─────────────────────┴───────────────────────┤
│ Recent Events (live feed) │
│ 10:05 — Agent #1247 minted by 0xABC │
│ 10:03 — Agent #892 funded +0.5 BNB │
│ 09:58 — Agent #445 status → inactive │
└─────────────────────────────────────────────┘
Critical:
High:
Medium:
function checkAlerts(event) {
if (event.name === "AgentWithdraw") {
const amount = parseFloat(event.args.amount);
if (amount > 10) {
sendAlert("HIGH", `Large withdrawal: ${amount} BNB from Agent #${event.args.tokenId}`);
}
}
if (event.name === "AgentCreated") {
const recentMints = getMintsInLastMinutes(5);
if (recentMints > 50) {
sendAlert("HIGH", `Rapid minting detected: ${recentMints} mints in 5 minutes`);
}
}
}
BAP-578 Daily Report — 2026-03-06
──────────────────────────────────
New mints: +14 (total: 1,247)
New owners: +6 (total: 423)
Funding inflows: +2.3 BNB
Withdrawals: -0.5 BNB
Net funding: +1.8 BNB
TVL: 45.3 BNB
Active agents: 892 (71.5%)
Metadata updates: 23
Notable: Agent #1200 funded with 5 BNB (largest single deposit this week)
BAP-578 Weekly Trends — W10 2026
─────────────────────────────────
Metric This Week Last Week Change
Mints 98 72 +36.1%
New owners 34 28 +21.4%
Funding volume 15.2 BNB 11.8 BNB +28.8%
Active ratio 71.5% 69.2% +2.3pp
Paid mint conv. 18.2% 15.7% +2.5pp
For production-grade indexing, use The Graph protocol to create a subgraph:
type Agent @entity {
id: ID!
tokenId: BigInt!
owner: Bytes!
balance: BigDecimal!
active: Boolean!
logicAddress: Bytes!
createdAt: BigInt!
persona: String
experience: String
vaultURI: String
vaultHash: Bytes!
isFreeMint: Boolean!
fundingEvents: [FundingEvent!]! @derivedFrom(field: "agent")
withdrawEvents: [WithdrawEvent!]! @derivedFrom(field: "agent")
}
type FundingEvent @entity {
id: ID!
agent: Agent!
funder: Bytes!
amount: BigDecimal!
blockNumber: BigInt!
timestamp: BigInt!
transactionHash: Bytes!
}
type WithdrawEvent @entity {
id: ID!
agent: Agent!
owner: Bytes!
amount: BigDecimal!
blockNumber: BigInt!
timestamp: BigInt!
transactionHash: Bytes!
}
type DailyStat @entity {
id: ID!
date: String!
mintCount: Int!
fundingVolume: BigDecimal!
withdrawVolume: BigDecimal!
activeAgents: Int!
uniqueOwners: Int!
}
# Top 10 funded agents
{
agents(orderBy: balance, orderDirection: desc, first: 10) {
tokenId
owner
balance
active
experience
}
}
# Daily minting stats for last 30 days
{
dailyStats(orderBy: date, orderDirection: desc, first: 30) {
date
mintCount
fundingVolume
activeAgents
}
}
# Agent history
{
agent(id: "17") {
tokenId
owner
balance
fundingEvents(orderBy: timestamp, orderDirection: desc) {
amount
funder
timestamp
}
}
}
If building a REST API for the analytics dashboard:
GET /api/metrics/summary → total minted, active, TVL, treasury
GET /api/metrics/mints?period=7d → mint count by day for last 7 days
GET /api/metrics/funding?period=30d → funding volume by day
GET /api/agents/:tokenId → single agent full profile
GET /api/agents/:tokenId/history → event history for agent
GET /api/owners/:address → portfolio for owner
GET /api/leaderboard/funded → top agents by balance
GET /api/leaderboard/owners → top owners by agent count
GET /api/alerts/recent → recent triggered alerts
When asked for analytics help, respond with:
bap578bap578-scannerbap578-businesstools
Use only when the user explicitly asks to stage, commit, push, and open a GitHub pull request in one flow using the GitHub CLI (`gh`).
development
Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like "the xlsx in my downloads") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.
development
Use this skill when turning messy workout information into clear logs, comparing user-provided sessions, surfacing trends or likely PRs, and suggesting realistic next-session steps.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.