| name | jb-event-explorer-ui |
| description | Browse and decode Juicebox project events. Filter by type, project, time. Decode Pay, CashOut, DistributePayouts, and all JB events. |
Juicebox V5 Event Explorer UI
Browse, filter, and decode all Juicebox protocol events. See payment history, redemptions, distributions, and configuration changes.
Uses Shared Components
This skill uses components from /shared/:
| Component | Purpose |
|---|
styles.css | Dark theme, buttons, cards, badges |
wallet-utils.js | Chain config, formatting utilities |
chain-config.json | RPC URLs, contract addresses |
abis/*.json | Event signatures |
Overview
┌─────────────────────────────────────────────────────────┐
│ Event Explorer │
├─────────────────────────────────────────────────────────┤
│ Project: [____] Chain: [▼] Event Type: [▼] [Search] │
├─────────────────────────────────────────────────────────┤
│ ┌─ Pay ─────────────────────────────── 2 min ago ───┐ │
│ │ 0xabc...def paid 1.5 ETH → Project 42 │ │
│ │ Tokens: 1,500 · Memo: "Supporting the project" │ │
│ └───────────────────────────────────────────────────┘ │
│ ┌─ CashOut ─────────────────────────── 15 min ago ──┐ │
│ │ 0x123...456 redeemed 500 tokens for 0.4 ETH │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Complete Event Explorer Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Juicebox Event Explorer</title>
<link rel="stylesheet" href="/shared/styles.css">
<style>
.event-card { background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: var(--radius-md); margin-bottom: 0.75rem; overflow: hidden; }
.event-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; cursor: pointer; }
.event-header:hover { background: var(--bg-hover); }
.event-type { display: flex; align-items: center; gap: 0.5rem; }
.event-time { font-size: 0.75rem; color: var(--text-muted); }
.event-summary { font-size: 0.875rem; padding: 0 1rem 0.75rem; }
.event-details { padding: 1rem; border-top: 1px solid var(--border-color); display: none; }
.event-details.open { : block; }
</style>
</head>
<body>
<div class="container">
<h1>Event Explorer</h1>
<p class="subtitle">Browse all Juicebox project events</p>
<div class="card">
<div class="filters">
<div class="filter-group">
<label>Project ID</label>
<input type="number" id="filter-project" placeholder="All projects" style="width: 120px;">
</div>
<div class="filter-group">
<label>Chain</label>
<select id="filter-chain" style="width: 130px;">
<option value="1">Ethereum</option>
<option value="11155111">Sepolia</option>
<option value="10">Optimism</option>
<option value="8453">Base</option>
<option value="42161">Arbitrum</option>
</select>
</div>
<div class="filter-group">
<label>Event Type</label>
<select id="filter-type" style="width: 150px;">
<option value="">All Events</option>
<option value="Pay">Payments</option>
<option value="CashOutTokens">Cash Outs</option>
<option value="SendPayouts">Distributions</option>
<option value="MintTokens">Token Mints</option>
<option value="LaunchProject">Project Launches</option>
<option value="QueueRulesets">Ruleset Changes</option>
</select>
</div>
<div class="filter-group">
<label>Time Range</label>
<select id="filter-time" style="width: 130px;">
<option value="1000">Last 1000 blocks</option>
<option value="5000">Last 5000 blocks</option>
<option value="10000">Last 10000 blocks</option>
<option value="50000">Last 50000 blocks</option>
</select>
</div>
<button onclick="loadEvents()">Search</button>
<div style="margin-left: auto;">
<label class="live-indicator" style="cursor: pointer;">
<input type="checkbox" id="live-toggle" onchange="toggleLive()" style="display: none;">
<span class="live-dot inactive" id="live-dot"></span>
Live Updates
</label>
</div>
</div>
</div>
<div class="card">
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="stat-total">-</div>
<div class="stat-label">Total Events</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-payments">-</div>
<div class="stat-label">Payments</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-volume">-</div>
<div class="stat-label">Volume (ETH)</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat-unique">-</div>
<div class="stat-label">Unique Addresses</div>
</div>
</div>
</div>
<div id="events-container">
<div class="loading">Select filters and search to load events</div>
</div>
<div class="pagination hidden" id="pagination"></div>
</div>
<script type="module">
import { createPublicClient, http, parseAbiItem } from 'https://esm.sh/viem';
import {
CHAIN_CONFIGS,
CHAINS,
loadChainConfig,
truncateAddress,
formatEth,
formatNumber,
formatTimeAgo
} from '/shared/wallet-utils.js';
let events = [];
let currentPage = 1;
let liveUpdateInterval = null;
const EVENTS_PER_PAGE = 20;
const EVENT_ABIS = {
'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)'),
'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)'),
'SendPayouts': parseAbiItem('event SendPayouts(uint256 indexed rulesetId, uint256 indexed rulesetCycleNumber, uint256 indexed projectId, address beneficiary, uint256 amount, uint256 amountPaidOut, uint256 fee, address caller)'),
'MintTokens': parseAbiItem('event MintTokens(address indexed beneficiary, uint256 indexed projectId, uint256 tokenCount, uint256 beneficiaryTokenCount, string memo, uint256 reservedPercent, address caller)'),
'LaunchProject': parseAbiItem(),
: ()
};
. = loadEvents;
. = toggleLive;
. = toggleEvent;
() {
projectId = .().;
chainId = (.().);
eventType = .().;
blockRange = (.().);
container = .();
container. = ;
{
config = ();
chainConfig = config.[chainId];
(!chainConfig) {
container. = ;
;
}
client = ({
: [chainId],
: (chainConfig.)
});
currentBlock = client.();
fromBlock = currentBlock - (blockRange);
terminalAddress = chainConfig..;
controllerAddress = chainConfig..;
(!terminalAddress) {
container. = ;
;
}
logs = client.({
: [terminalAddress, controllerAddress].(),
fromBlock,
:
});
events = [];
uniqueAddresses = ();
totalVolume = ;
paymentCount = ;
( log logs) {
{
parsed = ;
eventName = ;
( [name, abi] .()) {
{
decoded = ({
: [abi],
: log.,
: log.
});
(decoded) {
parsed = decoded;
eventName = name;
;
}
} (e) {
}
}
(!parsed) ;
(eventType && eventName !== eventType) ;
(projectId && parsed..?.() !== projectId) ;
block = client.({ : log. });
event = {
: eventName,
: parsed.,
: log.,
: log.,
: (block.),
chainId
};
events.(event);
(eventName === ) {
paymentCount++;
totalVolume += parsed.. || ;
uniqueAddresses.(parsed..);
}
(parsed..) {
uniqueAddresses.(parsed..);
}
} (e) {
}
}
events.( b. - a.);
.(). = events.;
.(). = paymentCount;
.(). = (totalVolume);
.(). = uniqueAddresses.;
currentPage = ;
();
} (error) {
.(error);
container. = ;
}
}
{ decodeEventLog } ;
() {
container = .();
start = (currentPage - ) * ;
pageEvents = events.(start, start + );
(pageEvents. === ) {
container. = ;
.()..();
;
}
container. = pageEvents.( (event, start + i)).();
totalPages = .(events. / );
(totalPages > ) {
pagination = .();
pagination..();
pagination. = ;
( p = ; p <= .(totalPages, ); p++) {
btn = .();
btn. = ;
btn. = p;
btn. = { currentPage = p; (); };
pagination.(btn);
}
} {
.()..();
}
}
() {
badge = (event.);
summary = (event);
details = (event);
timeAgo = (event.);
explorer = [event.]?. || ;
;
}
() {
badges = {
: { : , : },
: { : , : },
: { : , : },
: { : , : },
: { : , : },
: { : , : }
};
badges[name] || { : name, : };
}
() {
args = event.;
(event.) {
:
{
: ,
:
};
:
{
: ,
:
};
:
{
: ,
:
};
:
{
: ,
:
};
:
{
: ,
:
};
:
{
: ,
:
};
:
{
: event.,
: .(args)
};
}
}
() {
args = event.;
details = [];
explorer = [event.]?. || ;
( key .(args)) {
((key)) {
value = args[key];
( value === ) {
(key.().() || key.().() || key.().()) {
value = (value) + + (value) + ;
} {
value = value.();
}
} ( value === && value.() && value. === ) {
value = ;
}
details.({ : key, : (value) });
}
}
details.({ : , : event..() });
details;
}
() {
details = .();
details..();
}
() {
checkbox = .();
dot = .();
(checkbox.) {
dot..();
liveUpdateInterval = (loadEvents, );
} {
dot..();
(liveUpdateInterval);
}
}
</script>
</body>
</html>
Event Types Reference
| Event | Contract | Description |
|---|
Pay | Terminal | Payment received |
CashOutTokens | Terminal | Token redemption |
SendPayouts | Terminal | Payout distribution |
UseAllowance | Terminal | Surplus allowance used |
AddToBalance | Terminal | Balance added without minting |
MintTokens | Controller | Direct token mint |
BurnTokens | Controller | Token burn |
LaunchProject | Controller | New project created |
QueueRulesets | Controller | Ruleset queued |
SendReservedTokensToSplits | Controller | Reserved tokens distributed |
Related Skills
/jb-explorer-ui - Contract read/write interface
/jb-ruleset-timeline-ui - Ruleset history
/jb-bendystraw - Indexed event queries