Skip to content

Commit c719241

Browse files
committed
feat: add new semaphore explorer to apps
re #1
1 parent 95f15e6 commit c719241

File tree

19 files changed

+1392
-11
lines changed

19 files changed

+1392
-11
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ node_modules/
3030

3131
# TypeScript cache
3232
*.tsbuildinfo
33+
next-env.d.ts
3334

3435
# Optional npm cache directory
3536
.npm
3637
.DS_Store
38+
*.pem
3739

3840
# Output of 'npm pack'
3941
*.tgz
@@ -94,3 +96,13 @@ typechain-types
9496

9597
# Other
9698
snark-artifacts
99+
100+
# vercel
101+
.vercel
102+
103+
# local env files
104+
.env*.local
105+
106+
# next.js
107+
.next/
108+
out/

apps/explorer/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Cedoor
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

apps/explorer/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<h1 align="center">
2+
Semaphore explorer
3+
</h1>
4+
5+
<p align="center">
6+
<a href="https://github.com/semaphore-protocol" target="_blank">
7+
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
8+
</a>
9+
<a href="https://github.com/semaphore-protocol/extensions/blob/main/LICENSE">
10+
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/extensions">
11+
</a>
12+
<a href="https://prettier.io/" target="_blank">
13+
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier">
14+
</a>
15+
</p>
16+
17+
<div align="center">
18+
<h4>
19+
<a href="../../CONTRIBUTING.md">
20+
👥 Contributing
21+
</a>
22+
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
23+
<a href="../../CODE_OF_CONDUCT.md">
24+
🤝 Code of conduct
25+
</a>
26+
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
27+
<a href="https://github.com/semaphore-protocol/extensions/issues/new/choose">
28+
🔎 Issues
29+
</a>
30+
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
31+
<a href="https://semaphore.pse.dev/telegram">
32+
🗣️ Chat &amp; Support
33+
</a>
34+
</h4>
35+
</div>
36+
37+
| View and explore on-chain semaphore data from multiple networks in a user-friendly way. Powered by the Semaphore subgraph and the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/main/packages/data) library, this read-only web application provides a comprehensive and intuitive interface for analyzing blockchain data.
38+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39+
40+
## 📜 Usage
41+
42+
### Start
43+
44+
To navigate inside the `explorer` folder, run:
45+
46+
```bash
47+
cd benchmarks
48+
```
49+
50+
To start the web app in a local server, run:
51+
52+
```sh
53+
yarn dev
54+
```
55+
56+
### Build
57+
58+
To build the web app, run:
59+
60+
```
61+
yarn build
62+
```
63+
64+
The `build` command generates static content into the `build` directory that can be served by any static content hosting service.

apps/explorer/next.config.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
async redirects() {
4+
return [
5+
{
6+
source: "/",
7+
destination: "/sepolia",
8+
permanent: true
9+
}
10+
]
11+
},
12+
reactStrictMode: false
13+
}
14+
15+
export default nextConfig

apps/explorer/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "explorer",
3+
"private": true,
4+
"scripts": {
5+
"dev": "next dev",
6+
"build": "next build",
7+
"start": "next start",
8+
"lint": "next lint"
9+
},
10+
"dependencies": {
11+
"@headlessui/react": "^2.1.10",
12+
"@semaphore-protocol/data": "^4.3.1",
13+
"@semaphore-protocol/utils": "^4.3.1",
14+
"next": "14.2.14",
15+
"react": "^18",
16+
"react-dom": "^18",
17+
"react-icons": "^5.3.0"
18+
},
19+
"devDependencies": {
20+
"@types/node": "^20",
21+
"@types/react": "^18",
22+
"@types/react-dom": "^18",
23+
"postcss": "^8",
24+
"tailwindcss": "^3.4.1",
25+
"typescript": "^5"
26+
}
27+
}

apps/explorer/postcss.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** @type {import('postcss-load-config').Config} */
2+
const config = {
3+
plugins: {
4+
tailwindcss: {},
5+
},
6+
};
7+
8+
export default config;
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"use client"
2+
3+
import { GroupResponse, SemaphoreSubgraph } from "@semaphore-protocol/data"
4+
import { SupportedNetwork } from "@semaphore-protocol/utils"
5+
import { usePathname } from "next/navigation"
6+
import { useCallback, useEffect, useState } from "react"
7+
import SearchBar from "@/components/SearchBar"
8+
9+
export default function Group() {
10+
const pathname = usePathname()
11+
const network = pathname.split("/")[1] as SupportedNetwork
12+
const groupId = pathname.split("/")[2] as SupportedNetwork
13+
14+
const [group, setGroup] = useState<GroupResponse>()
15+
const [filteredCommitments, setFilteredCommitments] = useState<string[]>([])
16+
const [filteredProofs, setFilteredProofs] = useState<any[]>([])
17+
18+
useEffect(() => {
19+
const fetchData = async () => {
20+
const subgraph = new SemaphoreSubgraph(network)
21+
22+
const groupInfo = await subgraph.getGroup(groupId, {
23+
members: true,
24+
validatedProofs: true
25+
})
26+
27+
setGroup(groupInfo)
28+
29+
setFilteredCommitments(groupInfo.members || [])
30+
setFilteredProofs(groupInfo.validatedProofs || [])
31+
}
32+
33+
fetchData()
34+
}, [])
35+
36+
const filterCommitments = useCallback(
37+
(identityCommitment: string) => {
38+
if (group && group.members) {
39+
const identityCommitments = group.members.filter((member) =>
40+
!identityCommitment ? true : member.includes(identityCommitment)
41+
)
42+
43+
setFilteredCommitments(identityCommitments)
44+
}
45+
},
46+
[group]
47+
)
48+
49+
const filterProofs = useCallback(
50+
(proofMessage: string) => {
51+
if (group && group.validatedProofs) {
52+
const proofs = group.validatedProofs.filter((proof) =>
53+
!proofMessage ? true : proof.message.includes(proofMessage)
54+
)
55+
56+
setFilteredProofs(proofs)
57+
}
58+
},
59+
[group]
60+
)
61+
62+
return (
63+
group && (
64+
<div className="mx-auto max-w-7xl px-4 lg:px-8 pt-20">
65+
<div className="flex justify-center flex-col pb-10 font-[family-name:var(--font-geist-sans)]">
66+
<div className="flex justify-between gap-x-6 py-5">
67+
<div className="min-w-0 flex-auto">
68+
<p className="text-sm font-semibold leading-6 text-gray-800">ID: {group.id}</p>
69+
<p className="mt-1 truncate text-sm leading-6 text-gray-600">Admin: {group.admin}</p>
70+
</div>
71+
<div className="hidden shrink-0 sm:flex sm:flex-col sm:items-end">
72+
<p className="text-sm leading-6 text-gray-600">{group.members?.length} members</p>
73+
<p className="mt-1 text-sm leading-6 text-gray-600">
74+
{group.validatedProofs?.length} proofs
75+
</p>
76+
</div>
77+
</div>
78+
79+
<div className="flex gap-4 flex-col md:flex-row">
80+
<div className="min-w-0 flex-auto">
81+
<SearchBar
82+
className="mb-6"
83+
placeholder="Identity commitment"
84+
onChange={filterCommitments}
85+
/>
86+
87+
<ul className="divide-y divide-gray-300">
88+
{filteredCommitments.map((commitment) => (
89+
<li className="flex justify-between gap-x-6 py-2 px-5" key={commitment}>
90+
<p className="mt-1 truncate text-xs leading-5 text-gray-500">{commitment}</p>
91+
</li>
92+
))}
93+
</ul>
94+
</div>
95+
96+
<div className="min-w-0 flex-auto">
97+
<SearchBar className="mb-6" placeholder="Proof message" onChange={filterProofs} />
98+
99+
<ul className="divide-y divide-gray-300">
100+
{filteredProofs.map((proof) => (
101+
<li className="flex justify-between gap-x-6 py-2 px-5" key={proof.nullifier}>
102+
<p className="mt-1 truncate text-xs leading-5 text-gray-500">{proof.message}</p>
103+
</li>
104+
))}
105+
</ul>
106+
</div>
107+
</div>
108+
</div>
109+
</div>
110+
)
111+
)
112+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client"
2+
3+
import { GroupResponse, SemaphoreSubgraph } from "@semaphore-protocol/data"
4+
import { SupportedNetwork } from "@semaphore-protocol/utils"
5+
import { usePathname } from "next/navigation"
6+
import { useCallback, useEffect, useState } from "react"
7+
import SearchBar from "@/components/SearchBar"
8+
9+
export default function Network() {
10+
const pathname = usePathname()
11+
const network = (pathname.split("/")[1] || "sepolia") as SupportedNetwork
12+
13+
const [allGroups, setAllGroups] = useState<GroupResponse[]>([])
14+
const [filteredGroups, setFilteredGroups] = useState<GroupResponse[]>([])
15+
16+
useEffect(() => {
17+
const fetchData = async () => {
18+
const subgraph = new SemaphoreSubgraph(network)
19+
20+
const groups = await subgraph.getGroups({
21+
members: true,
22+
validatedProofs: true
23+
})
24+
25+
setAllGroups(groups)
26+
setFilteredGroups(groups.slice())
27+
}
28+
29+
fetchData()
30+
}, [])
31+
32+
const filterGroups = useCallback((groupId: string) => {
33+
const groups = allGroups.filter((group) => (!groupId ? true : group.id.includes(groupId)))
34+
35+
setFilteredGroups(groups)
36+
}, [])
37+
38+
return (
39+
allGroups && (
40+
<div className="mx-auto max-w-7xl px-4 lg:px-8 pt-20">
41+
<SearchBar className="mb-6" placeholder="Group ID" onChange={filterGroups} />
42+
43+
<div className="flex justify-center flex-col pb-10 font-[family-name:var(--font-geist-sans)]">
44+
<ul className="divide-y divide-gray-300 min-w-xl">
45+
{filteredGroups.map((group) => (
46+
<li key={group.id}>
47+
<a
48+
href={`/${network}/${group.id}`}
49+
className="flex justify-between gap-x-6 p-5 hover:bg-gray-200"
50+
>
51+
<div className="min-w-0 flex-auto">
52+
<p className="text-sm font-semibold leading-6 text-gray-800">ID: {group.id}</p>
53+
<p className="mt-1 truncate text-sm leading-6 text-gray-600">
54+
Admin: {group.admin}
55+
</p>
56+
</div>
57+
<div className="hidden shrink-0 sm:flex sm:flex-col sm:items-end">
58+
<p className="text-sm leading-6 text-gray-600">
59+
{group.members?.length} member
60+
{group.members?.length !== 1 ? "s" : ""}
61+
</p>
62+
<p className="mt-1 text-sm leading-6 text-gray-600">
63+
{group.validatedProofs?.length} proof
64+
{group.validatedProofs?.length !== 1 ? "s" : ""}
65+
</p>
66+
</div>
67+
</a>
68+
</li>
69+
))}
70+
</ul>
71+
</div>
72+
</div>
73+
)
74+
)
75+
}

apps/explorer/src/app/favicon.ico

25.3 KB
Binary file not shown.
66.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)