Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78dcb705c5 | ||
|
|
3c75453e69 | ||
|
|
e48099b0f1 | ||
|
|
64e313a541 | ||
|
|
662c3b5af4 | ||
|
|
f0ffd47fae | ||
|
|
c636317c2e | ||
|
|
41a2f2f7cb | ||
|
|
c591cc48e9 | ||
|
|
b2a33eaf58 | ||
|
|
8b419a024b | ||
|
|
1e53e6fc49 | ||
|
|
f9dd755b63 | ||
|
|
78edf03771 | ||
|
|
70b9bb0ddc | ||
|
|
a6bb9bba73 | ||
|
|
8cc9d7feae | ||
|
|
f72d228e51 | ||
|
|
e6d30cdb7a | ||
|
|
841ed4bbd9 | ||
|
|
437ade277a | ||
|
|
fb66869a3c | ||
|
|
8afa2252e2 | ||
|
|
7af714bb98 | ||
|
|
c434f8af5b | ||
|
|
9fec16129b | ||
|
|
482b64d031 | ||
|
|
7163e6f460 | ||
|
|
5d2e1ca5a3 | ||
|
|
031229939a | ||
|
|
9d24c8184a | ||
|
|
ae359322e9 | ||
|
|
33998d90f7 | ||
|
|
d9de7c1033 | ||
|
|
120f99b416 | ||
|
|
b95cb17135 | ||
|
|
507c252d36 | ||
|
|
82db6a1eb7 | ||
|
|
c60ab38882 | ||
|
|
cba5940f36 | ||
|
|
110e3de844 | ||
|
|
f60e85335d | ||
|
|
ef4cef65cd | ||
|
|
f69534683d | ||
|
|
4d849b48c2 | ||
|
|
531e16e0d7 | ||
|
|
f2575f2bec | ||
|
|
3bfc7ce2bc | ||
|
|
ef88c9c84c | ||
|
|
6580b6620f | ||
|
|
42d33e2af7 | ||
|
|
594c612318 | ||
|
|
fbfa92023d | ||
|
|
caed0aecc1 | ||
|
|
7bcc44c645 | ||
|
|
0ac390eaf0 | ||
|
|
5db4c61c28 | ||
|
|
37dc17d880 | ||
|
|
a04057f861 | ||
|
|
29e8f5852f | ||
|
|
26c76f453e | ||
|
|
9a1a1e7853 | ||
|
|
f583caa243 | ||
|
|
b399b923ab | ||
|
|
65e5d29dcd | ||
|
|
ba9782139e | ||
|
|
edef368976 | ||
|
|
77fddc9b16 | ||
|
|
69b2b8fd7b | ||
|
|
aba7d51f86 | ||
|
|
32d1b47964 | ||
|
|
cb3ff0fcb8 | ||
|
|
4d4338d3bc | ||
|
|
536410457e | ||
|
|
7469086a2a | ||
|
|
3496aa6478 | ||
|
|
60d5110905 | ||
|
|
0da64796ea | ||
|
|
41ab36d24c | ||
|
|
6bcd77fe5b | ||
|
|
4a221a43c7 | ||
|
|
37465288a7 | ||
|
|
c38b3c5255 | ||
|
|
dba7a6ca85 | ||
|
|
468a3314e3 | ||
|
|
a80b1136cd | ||
|
|
44328f1893 | ||
|
|
a0c1c27c35 | ||
|
|
f6be176df9 | ||
|
|
1a2eb3e18d | ||
|
|
44b25995e0 | ||
|
|
f1b3368529 | ||
|
|
5c64b09f51 | ||
|
|
fd08c3f2e7 | ||
|
|
01fa043b4d | ||
|
|
c10b57e3cf | ||
|
|
9a7e053f64 | ||
|
|
2e6b2d678a | ||
|
|
cf1c039545 | ||
|
|
0a496e5375 | ||
|
|
ff6835d1db | ||
|
|
b350cd4b66 | ||
|
|
f4508c4db5 | ||
|
|
2df42d49c0 | ||
|
|
fea6b8446c | ||
|
|
322051bd3a | ||
|
|
6ae1249618 | ||
|
|
8528e9e758 | ||
|
|
9bea6143a6 | ||
|
|
3b59bf6117 | ||
|
|
cecdcc3fb1 | ||
|
|
f57c2b4397 | ||
|
|
47f3651bc5 | ||
|
|
8006983643 | ||
|
|
3a7f18652f | ||
|
|
c7a4cf7121 | ||
|
|
66aa482b07 | ||
|
|
b89ed0ebb7 | ||
|
|
4cf00f33d0 | ||
|
|
a3309ab707 | ||
|
|
30449c4fe7 | ||
|
|
6b1f1a8c31 | ||
|
|
70c53b6203 | ||
|
|
3313be7f38 | ||
|
|
240fee73b4 | ||
|
|
7e85c1ddda | ||
|
|
18d724b166 | ||
|
|
f97a233798 | ||
|
|
e146f3e84e | ||
|
|
81fba57259 | ||
|
|
07515c5068 | ||
|
|
ad812f778b | ||
|
|
70727203fd | ||
|
|
b93acc884b | ||
|
|
c9e565a445 | ||
|
|
6cea8c5dcc | ||
|
|
5bd7cd93f7 | ||
|
|
da1b4f6d8f | ||
|
|
124f627d39 | ||
|
|
199dca2a3b | ||
|
|
51accfc939 | ||
|
|
1696294881 | ||
|
|
c85b758120 | ||
|
|
12fd0f7a80 | ||
|
|
78da99055d | ||
|
|
77e4770b53 | ||
|
|
799afc82f4 | ||
|
|
46fe2fc8f8 | ||
|
|
7bb9554388 | ||
|
|
d88a71ec4d | ||
|
|
2afc4ce1de | ||
|
|
64f7b24c53 | ||
|
|
88ba0af2d2 | ||
|
|
c2e8ba6856 | ||
|
|
f9a8d68641 | ||
|
|
df84100c22 | ||
|
|
b962339203 | ||
|
|
073f5a5772 | ||
|
|
b26f74a453 | ||
|
|
e9cac671f2 | ||
|
|
ecf8dcafb1 | ||
|
|
f90c99193a | ||
|
|
a826708320 | ||
|
|
43370202a7 | ||
|
|
a4d1180d26 | ||
|
|
5c42f9e09c | ||
|
|
c8362e373b | ||
|
|
06765f2f88 | ||
|
|
f26c84445e | ||
|
|
61a2d6adc2 | ||
|
|
eddd6b3dd5 | ||
|
|
fa561f8f00 | ||
|
|
c1842eefc3 | ||
|
|
320187ff89 | ||
|
|
abbf1a1d30 | ||
|
|
59269b067d | ||
|
|
21bd9fe540 | ||
|
|
f60a4c02f1 | ||
|
|
d012310ae1 | ||
|
|
f01cb91472 | ||
|
|
31e6954aff | ||
|
|
7518d938d1 | ||
|
|
083d8a03a4 | ||
|
|
16c9e90ae4 | ||
|
|
1fe2745594 |
1
.github/ISSUE_TEMPLATE/----project.md
vendored
@@ -13,6 +13,7 @@ A brief description of your project. In what way have you used Semaphore?
|
||||
**Other info**
|
||||
|
||||
- Name
|
||||
- Tagline
|
||||
- Icon
|
||||
|
||||
**Links**
|
||||
|
||||
14
.github/workflows/auto-assign.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: auto-assign
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/auto-assign@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
reviewers: org/core-devs
|
||||
1
.github/workflows/docs.yml
vendored
@@ -40,6 +40,5 @@ jobs:
|
||||
uses: crazy-max/ghaction-github-pages@v2.5.0
|
||||
with:
|
||||
build_dir: docs
|
||||
jekyll: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4
.gitignore
vendored
@@ -63,9 +63,7 @@ node_modules/
|
||||
# Production
|
||||
build
|
||||
dist
|
||||
docs/*
|
||||
!docs/CNAME
|
||||
!docs/index.html
|
||||
docs
|
||||
|
||||
# Hardhat
|
||||
artifacts
|
||||
|
||||
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
||||
@@ -41,3 +41,7 @@ yarn-error.log*
|
||||
|
||||
# other
|
||||
snark-artifacts
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
public
|
||||
194
README.md
@@ -3,7 +3,7 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
<img width="40" alt="Semaphore icon." src="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
<img width="40" alt="Semaphore icon" src="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
</picture>
|
||||
Semaphore
|
||||
</h1>
|
||||
@@ -52,7 +52,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
<a href="https://semaphore.pse.dev/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -61,9 +61,7 @@
|
||||
| Semaphore is a protocol, designed to be a simple and generic privacy layer for Ethereum DApps. Using zero knowledge, Ethereum users can prove their membership of a group and send signals such as votes or endorsements without revealing their original identity. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
|
||||
|
||||
---
|
||||
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.pse.dev](https://semaphore.pse.dev).
|
||||
|
||||
## 📦 Packages
|
||||
|
||||
@@ -96,7 +94,7 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
|
||||
<a href="/packages/identity">
|
||||
@semaphore-protocol/identity
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/identity">
|
||||
<a href="https://js.semaphore.pse.dev/modules/_semaphore_protocol_identity">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
@@ -118,7 +116,7 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
|
||||
<a href="/packages/group">
|
||||
@semaphore-protocol/group
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/group">
|
||||
<a href="https://js.semaphore.pse.dev/modules/_semaphore_protocol_group">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
@@ -140,7 +138,7 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
|
||||
<a href="/packages/proof">
|
||||
@semaphore-protocol/proof
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/proof">
|
||||
<a href="https://js.semaphore.pse.dev/modules/_semaphore_protocol_proof">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
@@ -162,7 +160,7 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
|
||||
<a href="/packages/data">
|
||||
@semaphore-protocol/data
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/data">
|
||||
<a href="https://js.semaphore.pse.dev/modules/_semaphore_protocol_data">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
@@ -217,8 +215,186 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/heyauthn">
|
||||
@semaphore-protocol/heyauthn
|
||||
</a>
|
||||
<a href="https://js.semaphore.pse.dev/modules/_semaphore_protocol_heyauthn">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
</table>
|
||||
|
||||
## 💡 Projects
|
||||
|
||||
The following are some of the internal and external projects that use Semaphore. If you want to include your project, open an [issue](https://github.com/semaphore-protocol/semaphore/issues/new?assignees=&labels=documentation++%F0%9F%93%96&template=----project.md&title=) or create a PR by adding the project information to the `projects.json` file.
|
||||
|
||||
<table>
|
||||
<th>Project</th>
|
||||
<th>Description</th>
|
||||
<th>Links</th>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://bandada.pse.dev">
|
||||
Bandada
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
An open-source system for managing privacy-preserving groups of anonymous individuals. Create and manage onchain and offchain Semaphore groups using UI or API.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/privacy-scaling-explorations/bandada">
|
||||
Github
|
||||
</a>|
|
||||
<a href="https://discord.com/invite/sF5CT5rzrR">
|
||||
Discord
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://explorer.semaphore.pse.dev">
|
||||
Semaphore Explorer
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Semaphore explorer for on-chain groups.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/semaphore-protocol/explorer">
|
||||
Github
|
||||
</a>|
|
||||
<a href="https://semaphore.pse.dev/discord">
|
||||
Discord
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://discord.com/api/oauth2/authorize?client_id=1082429985496772628&permissions=1024&scope=bot">
|
||||
Semaphore Discord Bot
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
A Discord bot for Semaphore.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/semaphore-protocol/discord-bot">
|
||||
Github
|
||||
</a>|
|
||||
<a href="https://semaphore.pse.dev/discord">
|
||||
Discord
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://developer.unirep.io">
|
||||
Unirep
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
Private and nonrepudiable reputation system based on ZKP.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/Unirep">
|
||||
Github
|
||||
</a>|
|
||||
<a href="https://discord.gg/VzMMDJmYc5">
|
||||
Discord
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://zk-proof-of-humanity.vercel.app">
|
||||
ZK Proof of Humanity
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
A project to allows humans, registered in Proof of Humanity, to prove their humanity without doxing.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/elmol/zk-proof-of-humanity">
|
||||
Github
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Plurality
|
||||
</td>
|
||||
<td>
|
||||
An Identity Lego Building Block for dapp creators that lets them identify their users without</br> using any third-party KYC provider or other middlemen, whilst preserving the privacy of users.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/Web3-Plurality">
|
||||
Github
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://zerotherapy.vercel.app">
|
||||
ZeroTherapy
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
AMA privacy application built with Semaphore.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/Pushpit07/ZeroTherapy">
|
||||
Github
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://bq2.netlify.app/">
|
||||
Block Qualified
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
On-chain and privacy preserving education platform built on Semaphore.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/0xdeenz/bq2">
|
||||
Github
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://stealthcomms.surge.sh/">
|
||||
StealthComms
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
A project that allows users to prove their membership in a group and send messages/signals without revealing their original identity.
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://github.com/atomniketh/zk-app">
|
||||
Github
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
</table>
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
123
docs/index.html
@@ -1,123 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html style="height: 100%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge" />
|
||||
<title>Semaphore packages</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A monorepo of Semaphore packages."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
margin: 0;
|
||||
background-color: #EAF0F4;
|
||||
color: #000;
|
||||
height: 100%;
|
||||
font-family: 'Courier New', monospace;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 0 20px;
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<div style="display: flex">
|
||||
<span style="margin-right: 5px">
|
||||
<img width="40" src="https://raw.githubusercontent.com/semaphore-protocol/website/main/static/img/semaphore-icon.svg">
|
||||
</span>
|
||||
<h1 style="margin: 0; font-size: 40px">Semaphore packages</h1>
|
||||
</div>
|
||||
<p style="max-width: 500px">
|
||||
A monorepo of Semaphore packages.
|
||||
</p>
|
||||
<ul style="list-style-type: none; padding: 0; margin: 0; margin-top: 10px"></ul>
|
||||
</div>
|
||||
<footer
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 15px 20px;
|
||||
background-color: #EAF0F4;
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 900px;
|
||||
"
|
||||
>
|
||||
<p style="margin: 0; font-size: 16px">
|
||||
Copyright © 2022 Ethereum Foundation
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
style="margin-right: 15px; text-decoration: none"
|
||||
target="_blank"
|
||||
href="https://github.com/semaphore-protocol/semaphore"
|
||||
>
|
||||
<i
|
||||
class="fa fa-github"
|
||||
style="font-size: 24px; color: #000"
|
||||
></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
<script>
|
||||
const url =
|
||||
"https://api.github.com/repos/semaphore-protocol/semaphore/contents?ref=gh-pages"
|
||||
|
||||
function insertLinks(packages) {
|
||||
const [element] = window.document.getElementsByTagName("ul")
|
||||
let html = ""
|
||||
|
||||
for (const package of packages) {
|
||||
html += `<li style="display: flex; align-items: center; margin-bottom: 8px">
|
||||
<a style="margin-right: 15px" target="_blank" href="https://github.com/semaphore-protocol/semaphore/tree/main/packages/${package}">
|
||||
<i class="fa fa-github" style="font-size: 24px; color: #000"></i>
|
||||
</a>
|
||||
<a style="color: #000; text-decoration: none; font-size: 16px"
|
||||
onmouseover="this.style.color='#404A4E';"
|
||||
onmouseout="this.style.color='#000';"
|
||||
target="_blank" href="https://semaphore-protocol.github.io/semaphore/${package}">
|
||||
@semaphore-protocol/${package} >
|
||||
</a></li>`
|
||||
}
|
||||
|
||||
element.innerHTML = html
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const ignore = [".nojekyll", "index.html", "CNAME"]
|
||||
const packages = data
|
||||
.map((c) => c.name)
|
||||
.filter((name) => !ignore.includes(name))
|
||||
|
||||
localStorage.setItem("packages", JSON.stringify(packages))
|
||||
|
||||
insertLinks(packages)
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
13
package.json
@@ -7,22 +7,23 @@
|
||||
"bugs": "https://github.com/semaphore-protocol/semaphore/issues",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:libraries": "yarn workspaces foreach -t run build",
|
||||
"build:libraries": "yarn workspaces foreach -t --no-private run build",
|
||||
"compile:contracts": "yarn workspace contracts compile",
|
||||
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
|
||||
"remove:template-files": "ts-node scripts/remove-template-files.ts",
|
||||
"test": "yarn test:libraries && yarn test:contracts",
|
||||
"test:libraries": "jest --coverage",
|
||||
"test:contracts": "yarn workspace contracts test:coverage",
|
||||
"lint": "eslint . --ext .js,.ts && yarn workspace contracts lint",
|
||||
"prettier": "prettier -c .",
|
||||
"prettier:write": "prettier -w .",
|
||||
"docs": "yarn workspaces foreach run docs",
|
||||
"docs": "typedoc --cname js.semaphore.pse.dev --githubPages true",
|
||||
"version:bump": "yarn workspaces foreach --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}",
|
||||
"version:publish": "yarn build:libraries && yarn workspaces foreach --no-private npm publish --tolerate-republish",
|
||||
"version:publish": "yarn build:libraries && yarn remove:template-files && yarn workspaces foreach --no-private npm publish --tolerate-republish --access public",
|
||||
"version:release": "changelogithub",
|
||||
"commit": "cz",
|
||||
"precommit": "lint-staged",
|
||||
"postinstall": "yarn download:snark-artifacts"
|
||||
"postinstall": "yarn download:snark-artifacts && husky install"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
@@ -49,11 +50,13 @@
|
||||
"@commitlint/cli": "^16.0.2",
|
||||
"@commitlint/config-conventional": "^16.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@types/circomlibjs": "^0.1.4",
|
||||
"@types/download": "^8.0.1",
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/node": "^17.0.9",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/snarkjs": "^0.7.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"babel-jest": "^27.4.6",
|
||||
@@ -67,6 +70,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jest": "^25.7.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^27.4.1",
|
||||
"jest-config": "^27.4.7",
|
||||
"lint-staged": "^12.1.7",
|
||||
@@ -75,6 +79,7 @@
|
||||
"rollup": "^2.64.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"tslib": "^2.3.1",
|
||||
"typedoc": "^0.25.1",
|
||||
"typescript": "^4.7.0"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
<a href="https://semaphore.pse.dev/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
To learn more about circuits visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org/docs/technical-reference/circuits).
|
||||
To learn more about circuits visit [semaphore.pse.dev](https://semaphore.pse.dev/docs/technical-reference/circuits).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ETHEREUM_URL=
|
||||
INFURA_API_KEY=
|
||||
ETHEREUM_PRIVATE_KEY=
|
||||
REPORT_GAS=false
|
||||
COINMARKETCAP_API_KEY=
|
||||
@@ -7,4 +7,4 @@ typechain-types
|
||||
|
||||
# Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
artifacts
|
||||
@@ -48,7 +48,7 @@ yarn deploy --semaphore <semaphore-address> --group <group-id> --network goerli
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Check the Semaphore contract addresses [here](https://semaphore.appliedzkp.org/docs/deployed-contracts#semaphore).
|
||||
> Check the Semaphore contract addresses [here](https://semaphore.pse.dev/docs/deployed-contracts#semaphore).
|
||||
|
||||
> **Warning**
|
||||
> The group id is a number!
|
||||
@@ -0,0 +1,30 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
contract Feedback {
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 public groupId;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
}
|
||||
|
||||
function sendFeedback(
|
||||
uint256 feedback,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
|
||||
}
|
||||
}
|
||||
77
packages/cli-template-contracts-hardhat/hardhat.config.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomicfoundation/hardhat-chai-matchers"
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import "@typechain/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy"
|
||||
|
||||
dotenvConfig()
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: config.solidity,
|
||||
paths: {
|
||||
sources: config.paths.contracts,
|
||||
tests: config.paths.tests,
|
||||
cache: config.paths.cache,
|
||||
artifacts: config.paths.build.contracts
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337
|
||||
},
|
||||
...getNetworks()
|
||||
},
|
||||
gasReporter: {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
},
|
||||
typechain: {
|
||||
outDir: config.paths.build.typechain,
|
||||
target: "ethers-v5"
|
||||
}
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
@@ -1,29 +1,32 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli-template-hardhat",
|
||||
"version": "3.2.2",
|
||||
"name": "@semaphore-protocol/cli-template-contracts-hardhat",
|
||||
"version": "3.14.0",
|
||||
"description": "Semaphore Hardhat template.",
|
||||
"license": "Unlicense",
|
||||
"files": [
|
||||
".gitignore",
|
||||
".env.example",
|
||||
"files.tgz",
|
||||
"contracts/",
|
||||
"scripts/",
|
||||
"tasks/",
|
||||
"test/",
|
||||
".env.example",
|
||||
"hardhat.config.ts",
|
||||
"tsconfig.json",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "hardhat node",
|
||||
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
|
||||
"compile": "hardhat compile",
|
||||
"deploy": "hardhat deploy",
|
||||
"test": "hardhat test",
|
||||
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
|
||||
"deploy": "yarn compile && hardhat deploy",
|
||||
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage"
|
||||
"test:coverage": "hardhat coverage",
|
||||
"typechain": "hardhat typechain",
|
||||
"prepublish": "tar -czf files.tgz .gitignore"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethersproject/abi": "^5.4.7",
|
||||
@@ -33,10 +36,10 @@
|
||||
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.0.0",
|
||||
"@semaphore-protocol/group": "3.2.2",
|
||||
"@semaphore-protocol/hardhat": "3.2.2",
|
||||
"@semaphore-protocol/identity": "3.2.2",
|
||||
"@semaphore-protocol/proof": "3.2.2",
|
||||
"@semaphore-protocol/group": "3.14.0",
|
||||
"@semaphore-protocol/hardhat": "3.14.0",
|
||||
"@semaphore-protocol/identity": "3.14.0",
|
||||
"@semaphore-protocol/proof": "3.14.0",
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"@typechain/hardhat": "^6.1.2",
|
||||
"@types/chai": "^4.2.0",
|
||||
@@ -55,6 +58,21 @@
|
||||
"typescript": ">=4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/contracts": "3.2.2"
|
||||
"@semaphore-protocol/contracts": "3.14.0"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"build": {
|
||||
"snark-artifacts": "./build/snark-artifacts",
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import download from "download"
|
||||
import fs from "fs"
|
||||
import { config } from "../package.json"
|
||||
|
||||
async function main() {
|
||||
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
|
||||
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
|
||||
|
||||
if (!fs.existsSync(snarkArtifactsPath)) {
|
||||
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
|
||||
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
|
||||
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy", "Deploy a Greeter contract")
|
||||
task("deploy", "Deploy a Feedback contract")
|
||||
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
|
||||
.addOptionalParam("group", "Group id", "42", types.string)
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
@@ -13,15 +13,19 @@ task("deploy", "Deploy a Greeter contract")
|
||||
semaphoreAddress = semaphore.address
|
||||
}
|
||||
|
||||
const Greeter = await ethers.getContractFactory("Greeter")
|
||||
|
||||
const greeter = await Greeter.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await greeter.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Greeter contract has been deployed to: ${greeter.address}`)
|
||||
if (!groupId) {
|
||||
groupId = process.env.GROUP_ID
|
||||
}
|
||||
|
||||
return greeter
|
||||
const FeedbackFactory = await ethers.getContractFactory("Feedback")
|
||||
|
||||
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await feedbackContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
|
||||
}
|
||||
|
||||
return feedbackContract
|
||||
})
|
||||
69
packages/cli-template-contracts-hardhat/test/Feedback.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { formatBytes32String } from "ethers/lib/utils"
|
||||
import { run } from "hardhat"
|
||||
// @ts-ignore: typechain folder will be generated after contracts compilation
|
||||
import { Feedback } from "../build/typechain"
|
||||
import { config } from "../package.json"
|
||||
|
||||
describe("Feedback", () => {
|
||||
let feedbackContract: Feedback
|
||||
let semaphoreContract: string
|
||||
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
const users: Identity[] = []
|
||||
|
||||
before(async () => {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
|
||||
semaphoreContract = semaphore
|
||||
|
||||
users.push(new Identity())
|
||||
users.push(new Identity())
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for await (const [i, user] of users.entries()) {
|
||||
const transaction = feedbackContract.joinGroup(user.commitment)
|
||||
|
||||
group.addMember(user.commitment)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, i, user.commitment, group.root)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# sendFeedback", () => {
|
||||
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
|
||||
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
|
||||
|
||||
it("Should allow users to send feedback anonymously", async () => {
|
||||
const feedback = formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1], group, groupId, feedback, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
const transaction = feedbackContract.sendFeedback(
|
||||
feedback,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "ProofVerified")
|
||||
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
|
||||
})
|
||||
})
|
||||
})
|
||||
15
packages/cli-template-contracts-hardhat/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"typeRoots": ["node_modules/@types", "types"]
|
||||
},
|
||||
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
//SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
/// @title Greeter contract.
|
||||
/// @dev The following code is just a example to show how Semaphore can be used.
|
||||
contract Greeter {
|
||||
event NewGreeting(bytes32 greeting);
|
||||
event NewUser(uint256 identityCommitment, bytes32 username);
|
||||
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 groupId;
|
||||
mapping(uint256 => bytes32) users;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment, bytes32 username) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
|
||||
users[identityCommitment] = username;
|
||||
|
||||
emit NewUser(identityCommitment, username);
|
||||
}
|
||||
|
||||
function greet(
|
||||
bytes32 greeting,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, uint256(greeting), nullifierHash, groupId, proof);
|
||||
|
||||
emit NewGreeting(greeting);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import "@nomicfoundation/hardhat-toolbox"
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import "./tasks/deploy"
|
||||
|
||||
dotenvConfig()
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (process.env.ETHEREUM_URL && process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: "0.8.4",
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337
|
||||
},
|
||||
...getNetworks()
|
||||
},
|
||||
gasReporter: {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import download from "download"
|
||||
import { existsSync } from "fs"
|
||||
import { ethers, run } from "hardhat"
|
||||
// @ts-ignore: typechain-types folder will be generated after contracts compilation
|
||||
import { Greeter } from "../typechain-types"
|
||||
|
||||
describe("Greeter", () => {
|
||||
let greeter: Greeter
|
||||
|
||||
const snarkArtifactsURL = "https://www.trusted-setup-pse.org/semaphore/20"
|
||||
const snarkArtifactsPath = "./artifacts/snark"
|
||||
|
||||
const users: any[] = []
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
|
||||
before(async () => {
|
||||
if (!existsSync(`${snarkArtifactsPath}/semaphore.wasm`)) {
|
||||
await download(`${snarkArtifactsURL}/semaphore.wasm`, `${snarkArtifactsPath}`)
|
||||
await download(`${snarkArtifactsURL}/semaphore.zkey`, `${snarkArtifactsPath}`)
|
||||
}
|
||||
|
||||
greeter = await run("deploy", { logs: false, group: groupId })
|
||||
|
||||
users.push({
|
||||
identity: new Identity(),
|
||||
username: ethers.utils.formatBytes32String("anon1")
|
||||
})
|
||||
|
||||
users.push({
|
||||
identity: new Identity(),
|
||||
username: ethers.utils.formatBytes32String("anon2")
|
||||
})
|
||||
|
||||
group.addMember(users[0].identity.commitment)
|
||||
group.addMember(users[1].identity.commitment)
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for (let i = 0; i < group.members.length; i += 1) {
|
||||
const transaction = greeter.joinGroup(group.members[i], users[i].username)
|
||||
|
||||
await expect(transaction).to.emit(greeter, "NewUser").withArgs(group.members[i], users[i].username)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# greet", () => {
|
||||
it("Should allow users to greet", async () => {
|
||||
const greeting = ethers.utils.formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1].identity, group, groupId, greeting, {
|
||||
wasmFilePath: `${snarkArtifactsPath}/semaphore.wasm`,
|
||||
zkeyFilePath: `${snarkArtifactsPath}/semaphore.zkey`
|
||||
})
|
||||
|
||||
const transaction = greeter.greet(
|
||||
greeting,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction).to.emit(greeter, "NewGreeting").withArgs(greeting)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./tasks", "./test", "./typechain-types"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
13
packages/cli-template-monorepo-ethers/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
#root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
10
packages/cli-template-monorepo-ethers/.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
DEFAULT_NETWORK=localhost
|
||||
INFURA_API_KEY=
|
||||
ETHEREUM_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||
FEEDBACK_CONTRACT_ADDRESS=0x5fc8d32690cc91d4c39d9d3abcbd16989f875707
|
||||
SEMAPHORE_CONTRACT_ADDRESS=0xdc64a140aa3e981100a9beca4e685f962f0cf6c9
|
||||
OPENZEPPELIN_AUTOTASK_WEBHOOK=
|
||||
GROUP_ID=42
|
||||
REPORT_GAS=false
|
||||
COINMARKETCAP_API_KEY=
|
||||
ETHERSCAN_API_KEY=
|
||||
45
packages/cli-template-monorepo-ethers/.eslintignore
Normal file
@@ -0,0 +1,45 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# docs
|
||||
docs
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
cache
|
||||
contract-artifacts
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# The Graph
|
||||
generated
|
||||
|
||||
# Auto Generated PWA files
|
||||
**/public/sw.js
|
||||
**/public/workbox-*.js
|
||||
**/public/worker-*.js
|
||||
**/public/sw.js.map
|
||||
**/public/workbox-*.js.map
|
||||
**/public/worker-*.js.map
|
||||
36
packages/cli-template-monorepo-ethers/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"extends": ["airbnb-base", "airbnb-typescript/base", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"project": ["./**/tsconfig.json"]
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"no-underscore-dangle": "off",
|
||||
"no-alert": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-relative-packages": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"no-bitwise": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-console": ["warn", { "allow": ["info", "warn", "error"] }],
|
||||
"@typescript-eslint/lines-between-class-members": "off",
|
||||
"no-param-reassign": "off",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "variable",
|
||||
"format": ["camelCase", "PascalCase", "UPPER_CASE", "snake_case"],
|
||||
"leadingUnderscore": "allow"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
122
packages/cli-template-monorepo-ethers/.gitignore
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Cargo
|
||||
target
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
coverage.json
|
||||
*.lcov
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# Generate output
|
||||
dist
|
||||
build
|
||||
cache
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
next-env.d.ts
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
*.DS_Store
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.pnp.js
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# The Graph
|
||||
generated
|
||||
|
||||
# Auto Generated PWA files
|
||||
**/public/sw.js
|
||||
**/public/workbox-*.js
|
||||
**/public/worker-*.js
|
||||
**/public/sw.js.map
|
||||
**/public/workbox-*.js.map
|
||||
**/public/worker-*.js.map
|
||||
|
||||
#amplify-do-not-edit-begin
|
||||
amplify/\#current-cloud-backend
|
||||
amplify/.config/local-*
|
||||
amplify/logs
|
||||
amplify/mock-data
|
||||
amplify/mock-api-resources
|
||||
amplify/backend/amplify-meta.json
|
||||
amplify/backend/.temp
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
aws-exports.js
|
||||
awsconfiguration.json
|
||||
amplifyconfiguration.json
|
||||
amplifyconfiguration.dart
|
||||
amplify-build-config.json
|
||||
amplify-gradle-config.json
|
||||
amplifytools.xcconfig
|
||||
.secret-*
|
||||
**.sample
|
||||
#amplify-do-not-edit-end
|
||||
|
||||
# Others
|
||||
files.tgz
|
||||
45
packages/cli-template-monorepo-ethers/.prettierignore
Normal file
@@ -0,0 +1,45 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# docs
|
||||
docs
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
cache
|
||||
contract-artifacts
|
||||
|
||||
# github
|
||||
.github/ISSUE_TEMPLATE
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# The Graph
|
||||
generated
|
||||
|
||||
# Auto Generated PWA files
|
||||
**/public/sw.js
|
||||
**/public/workbox-*.js
|
||||
**/public/worker-*.js
|
||||
**/public/sw.js.map
|
||||
**/public/workbox-*.js.map
|
||||
**/public/worker-*.js.map
|
||||
5
packages/cli-template-monorepo-ethers/.prettierrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
||||
28
packages/cli-template-monorepo-ethers/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
1
packages/cli-template-monorepo-ethers/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id
vendored
Normal file
@@ -0,0 +1 @@
|
||||
b3cadff6efb37a12712d12c2553ec703dbcaa4dd
|
||||
9
packages/cli-template-monorepo-ethers/.yarnrc.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
checksumBehavior: update
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||
59
packages/cli-template-monorepo-ethers/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Semaphore Hardhat + Next.js + SemaphoreEthers template
|
||||
|
||||
This project is a complete application that demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract. It also contains a frontend to play around with the contract.
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
Copy the `.env.example` file as `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
and add your environment variables or run the app in a local network.
|
||||
|
||||
### Local server
|
||||
|
||||
You can start your app locally with:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### Deploy the contract
|
||||
|
||||
1. Go to the `apps/contracts` directory and deploy your contract:
|
||||
|
||||
```bash
|
||||
yarn deploy --semaphore <semaphore-address> --group <group-id> --network arbitrum-goerli
|
||||
```
|
||||
|
||||
2. Update your `.env` file with your new contract address, the group id and the semaphore contract address.
|
||||
|
||||
3. Copy your contract artifacts from `apps/contracts/build/contracts/contracts` folder to `apps/web-app/contract-artifacts` folders manually. Or run `yarn copy:contract-artifacts` in the project root to do it automatically.
|
||||
|
||||
> **Note**
|
||||
> Check the Semaphore contract addresses [here](https://semaphore.pse.dev/docs/deployed-contracts).
|
||||
|
||||
> **Warning**
|
||||
> The group id is a number!
|
||||
|
||||
### Code quality and formatting
|
||||
|
||||
Run [ESLint](https://eslint.org/) to analyze the code and catch bugs:
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
Run [Prettier](https://prettier.io/) to check formatting rules:
|
||||
|
||||
```bash
|
||||
yarn prettier
|
||||
```
|
||||
|
||||
or to automatically format the code:
|
||||
|
||||
```bash
|
||||
yarn prettier:write
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "solhint:default"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
contract Feedback {
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 public groupId;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
}
|
||||
|
||||
function sendFeedback(
|
||||
uint256 feedback,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import "@nomicfoundation/hardhat-chai-matchers"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomiclabs/hardhat-etherscan"
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import "@typechain/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import { resolve } from "path"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy"
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "../../.env") })
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
"arbitrum-goerli": {
|
||||
url: "https://goerli-rollup.arbitrum.io/rpc",
|
||||
chainId: 421613,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: "https://arb1.arbitrum.io/rpc",
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: config.solidity,
|
||||
paths: {
|
||||
sources: config.paths.contracts,
|
||||
tests: config.paths.tests,
|
||||
cache: config.paths.cache,
|
||||
artifacts: config.paths.build.contracts
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337
|
||||
},
|
||||
...getNetworks()
|
||||
},
|
||||
gasReporter: {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
},
|
||||
typechain: {
|
||||
outDir: config.paths.build.typechain,
|
||||
target: "ethers-v5"
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: process.env.ETHERSCAN_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "monorepo-ethers-contracts",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
|
||||
"compile": "hardhat compile",
|
||||
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
|
||||
"deploy": "yarn compile && hardhat deploy",
|
||||
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage",
|
||||
"typechain": "hardhat typechain",
|
||||
"lint": "solhint 'contracts/**/*.sol'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.7",
|
||||
"@semaphore-protocol/group": "3.14.0",
|
||||
"@semaphore-protocol/hardhat": "3.14.0",
|
||||
"@semaphore-protocol/identity": "3.14.0",
|
||||
"@semaphore-protocol/proof": "3.14.0",
|
||||
"@typechain/ethers-v5": "^10.0.0",
|
||||
"@typechain/hardhat": "^6.0.0",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/download": "^8.0.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"chai": "^4.2.0",
|
||||
"dotenv": "^14.3.2",
|
||||
"download": "^8.0.0",
|
||||
"ethers": "^5.0.0",
|
||||
"hardhat": "^2.8.4",
|
||||
"hardhat-gas-reporter": "^1.0.8",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.19",
|
||||
"solhint": "^3.3.6",
|
||||
"solhint-plugin-prettier": "^0.0.5",
|
||||
"solidity-coverage": "^0.7.21",
|
||||
"typechain": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/contracts": "3.14.0"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"build": {
|
||||
"snark-artifacts": "./build/snark-artifacts",
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import download from "download"
|
||||
import fs from "fs"
|
||||
import { config } from "../package.json"
|
||||
|
||||
async function main() {
|
||||
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
|
||||
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
|
||||
|
||||
if (!fs.existsSync(snarkArtifactsPath)) {
|
||||
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
|
||||
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
|
||||
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy", "Deploy a Feedback contract")
|
||||
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
|
||||
.addOptionalParam("group", "Group id", "42", types.string)
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
|
||||
if (!semaphoreAddress) {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs
|
||||
})
|
||||
|
||||
semaphoreAddress = semaphore.address
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
groupId = process.env.GROUP_ID
|
||||
}
|
||||
|
||||
const FeedbackFactory = await ethers.getContractFactory("Feedback")
|
||||
|
||||
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await feedbackContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
|
||||
}
|
||||
|
||||
return feedbackContract
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { formatBytes32String } from "ethers/lib/utils"
|
||||
import { run } from "hardhat"
|
||||
// @ts-ignore: typechain folder will be generated after contracts compilation
|
||||
import { Feedback } from "../build/typechain"
|
||||
import { config } from "../package.json"
|
||||
|
||||
describe("Feedback", () => {
|
||||
let feedbackContract: Feedback
|
||||
let semaphoreContract: string
|
||||
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
const users: Identity[] = []
|
||||
|
||||
before(async () => {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
|
||||
semaphoreContract = semaphore
|
||||
|
||||
users.push(new Identity())
|
||||
users.push(new Identity())
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for await (const [i, user] of users.entries()) {
|
||||
const transaction = feedbackContract.joinGroup(user.commitment)
|
||||
|
||||
group.addMember(user.commitment)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, i, user.commitment, group.root)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# sendFeedback", () => {
|
||||
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
|
||||
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
|
||||
|
||||
it("Should allow users to send feedback anonymously", async () => {
|
||||
const feedback = formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1], group, groupId, feedback, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
const transaction = feedbackContract.sendFeedback(
|
||||
feedback,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "ProofVerified")
|
||||
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"typeRoots": ["node_modules/@types", "types"]
|
||||
},
|
||||
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "Feedback",
|
||||
"sourceName": "contracts/Feedback.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "semaphoreAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "_groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "groupId",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "joinGroup",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "semaphore",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract ISemaphore",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "feedback",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "nullifierHash",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[8]",
|
||||
"name": "proof",
|
||||
"type": "uint256[8]"
|
||||
}
|
||||
],
|
||||
"name": "sendFeedback",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x608060405234801561001057600080fd5b506040516106e13803806106e18339818101604052810190610032919061013c565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639c1121416001546014306040518463ffffffff1660e01b81526004016100d9939291906101a5565b600060405180830381600087803b1580156100f357600080fd5b505af1158015610107573d6000803e3d6000fd5b505050505050610258565b6000815190506101218161022a565b92915050565b60008151905061013681610241565b92915050565b6000806040838503121561014f57600080fd5b600061015d85828601610112565b925050602061016e85828601610127565b9150509250929050565b610181816101dc565b82525050565b61019081610218565b82525050565b61019f8161020e565b82525050565b60006060820190506101ba6000830186610196565b6101c76020830185610187565b6101d46040830184610178565b949350505050565b60006101e7826101ee565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102238261020e565b9050919050565b610233816101dc565b811461023e57600080fd5b50565b61024a8161020e565b811461025557600080fd5b50565b61047a806102676000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
|
||||
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const fs = require("fs")
|
||||
const withPWA = require("next-pwa")
|
||||
|
||||
if (!fs.existsSync("./.env")) {
|
||||
// eslint-disable-next-line global-require
|
||||
require("dotenv").config({ path: "../../.env" })
|
||||
}
|
||||
|
||||
const nextConfig = withPWA({
|
||||
dest: "public",
|
||||
disable: process.env.NODE_ENV === "development"
|
||||
})({
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true
|
||||
},
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
env: {
|
||||
DEFAULT_NETWORK: process.env.DEFAULT_NETWORK,
|
||||
INFURA_API_KEY: process.env.INFURA_API_KEY,
|
||||
ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY,
|
||||
FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS,
|
||||
SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
DEFAULT_NETWORK: process.env.DEFAULT_NETWORK,
|
||||
FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS,
|
||||
SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS,
|
||||
OPENZEPPELIN_AUTOTASK_WEBHOOK: process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK,
|
||||
GROUP_ID: process.env.GROUP_ID
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
if (!isServer) {
|
||||
config.resolve.fallback = {
|
||||
fs: false
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "monorepo-ethers-web-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"export": "next export",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/font": "13.0.3",
|
||||
"@semaphore-protocol/data": "3.14.0",
|
||||
"@semaphore-protocol/group": "3.14.0",
|
||||
"@semaphore-protocol/identity": "3.14.0",
|
||||
"@semaphore-protocol/proof": "3.14.0",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "18.0.8",
|
||||
"dotenv": "^16.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"next": "13.0.3",
|
||||
"next-pwa": "^5.6.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 827 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"theme_color": "#ebedff",
|
||||
"background_color": "#ebedff",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"name": "Semaphore Boilerplate",
|
||||
"short_name": "Semaphore",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export type StepperProps = {
|
||||
step: number
|
||||
onPrevClick?: () => void
|
||||
onNextClick?: () => void
|
||||
}
|
||||
|
||||
export default function Stepper({ step, onPrevClick, onNextClick }: StepperProps) {
|
||||
return (
|
||||
<div className="stepper">
|
||||
{onPrevClick !== undefined ? (
|
||||
<button className="button-stepper" disabled={!onPrevClick} onClick={onPrevClick || undefined}>
|
||||
Prev
|
||||
</button>
|
||||
) : (
|
||||
<span></span>
|
||||
)}
|
||||
|
||||
<p>{step.toString()}/3</p>
|
||||
|
||||
{onNextClick !== undefined ? (
|
||||
<button className="button-stepper" disabled={!onNextClick} onClick={onNextClick || undefined}>
|
||||
Next
|
||||
</button>
|
||||
) : (
|
||||
<span></span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from "react"
|
||||
|
||||
export type LogsContextType = {
|
||||
_logs: string
|
||||
setLogs: (logs: string) => void
|
||||
}
|
||||
|
||||
export default React.createContext<LogsContextType>({
|
||||
_logs: "",
|
||||
setLogs: (logs: string) => logs
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react"
|
||||
|
||||
export type SemaphoreContextType = {
|
||||
_users: string[]
|
||||
_feedback: string[]
|
||||
refreshUsers: () => Promise<void>
|
||||
addUser: (user: string) => void
|
||||
refreshFeedback: () => Promise<void>
|
||||
addFeedback: (feedback: string) => void
|
||||
}
|
||||
|
||||
export default React.createContext<SemaphoreContextType>({
|
||||
_users: [],
|
||||
_feedback: [],
|
||||
refreshUsers: () => Promise.resolve(),
|
||||
addUser: () => {},
|
||||
refreshFeedback: () => Promise.resolve(),
|
||||
addFeedback: () => {}
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { SemaphoreEthers } from "@semaphore-protocol/data"
|
||||
import { BigNumber, utils } from "ethers"
|
||||
import getNextConfig from "next/config"
|
||||
import { useCallback, useState } from "react"
|
||||
import { SemaphoreContextType } from "../context/SemaphoreContext"
|
||||
|
||||
const { publicRuntimeConfig: env } = getNextConfig()
|
||||
|
||||
const ethereumNetwork = env.DEFAULT_NETWORK === "localhost" ? "http://localhost:8545" : env.DEFAULT_NETWORK
|
||||
|
||||
export default function useSemaphore(): SemaphoreContextType {
|
||||
const [_users, setUsers] = useState<any[]>([])
|
||||
const [_feedback, setFeedback] = useState<string[]>([])
|
||||
|
||||
const refreshUsers = useCallback(async (): Promise<void> => {
|
||||
const semaphore = new SemaphoreEthers(ethereumNetwork, {
|
||||
address: env.SEMAPHORE_CONTRACT_ADDRESS
|
||||
})
|
||||
|
||||
const members = await semaphore.getGroupMembers(env.GROUP_ID)
|
||||
|
||||
setUsers(members)
|
||||
}, [])
|
||||
|
||||
const addUser = useCallback(
|
||||
(user: any) => {
|
||||
setUsers([..._users, user])
|
||||
},
|
||||
[_users]
|
||||
)
|
||||
|
||||
const refreshFeedback = useCallback(async (): Promise<void> => {
|
||||
const semaphore = new SemaphoreEthers(ethereumNetwork, {
|
||||
address: env.SEMAPHORE_CONTRACT_ADDRESS
|
||||
})
|
||||
|
||||
const proofs = await semaphore.getGroupVerifiedProofs(env.GROUP_ID)
|
||||
|
||||
setFeedback(proofs.map(({ signal }: any) => utils.parseBytes32String(BigNumber.from(signal).toHexString())))
|
||||
}, [])
|
||||
|
||||
const addFeedback = useCallback(
|
||||
(feedback: string) => {
|
||||
setFeedback([..._feedback, feedback])
|
||||
},
|
||||
[_feedback]
|
||||
)
|
||||
|
||||
return {
|
||||
_users,
|
||||
_feedback,
|
||||
refreshUsers,
|
||||
addUser,
|
||||
refreshFeedback,
|
||||
addFeedback
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import "../styles/globals.css"
|
||||
import type { AppProps } from "next/app"
|
||||
import Head from "next/head"
|
||||
import { useRouter } from "next/router"
|
||||
import { useEffect, useState } from "react"
|
||||
import LogsContext from "../context/LogsContext"
|
||||
import SemaphoreContext from "../context/SemaphoreContext"
|
||||
import useSemaphore from "../hooks/useSemaphore"
|
||||
import { Inter } from "@next/font/google"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter()
|
||||
const semaphore = useSemaphore()
|
||||
const [_logs, setLogs] = useState<string>("")
|
||||
|
||||
useEffect(() => {
|
||||
semaphore.refreshUsers()
|
||||
semaphore.refreshFeedback()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={inter.className}>
|
||||
<Head>
|
||||
<title>Semaphore template</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="theme-color" content="#ebedff" />
|
||||
</Head>
|
||||
|
||||
<div>
|
||||
<div className="container">
|
||||
<div id="body">
|
||||
<SemaphoreContext.Provider value={semaphore}>
|
||||
<LogsContext.Provider
|
||||
value={{
|
||||
_logs,
|
||||
setLogs
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</LogsContext.Provider>
|
||||
</SemaphoreContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer">
|
||||
{_logs.endsWith("...")}
|
||||
<p>{_logs || `Current step: ${router.route}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Contract, providers, Wallet } from "ethers"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import Feedback from "../../../contract-artifacts/Feedback.json"
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (typeof process.env.FEEDBACK_CONTRACT_ADDRESS !== "string") {
|
||||
throw new Error("Please, define FEEDBACK_CONTRACT_ADDRESS in your .env file")
|
||||
}
|
||||
|
||||
if (typeof process.env.DEFAULT_NETWORK !== "string") {
|
||||
throw new Error("Please, define DEFAULT_NETWORK in your .env file")
|
||||
}
|
||||
|
||||
if (typeof process.env.INFURA_API_KEY !== "string") {
|
||||
throw new Error("Please, define INFURA_API_KEY in your .env file")
|
||||
}
|
||||
|
||||
if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") {
|
||||
throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file")
|
||||
}
|
||||
|
||||
const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY
|
||||
const ethereumNetwork = process.env.DEFAULT_NETWORK
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS
|
||||
|
||||
const provider =
|
||||
ethereumNetwork === "localhost"
|
||||
? new providers.JsonRpcProvider()
|
||||
: new providers.InfuraProvider(ethereumNetwork, infuraApiKey)
|
||||
|
||||
const signer = new Wallet(ethereumPrivateKey, provider)
|
||||
const contract = new Contract(contractAddress, Feedback.abi, signer)
|
||||
|
||||
const { feedback, merkleTreeRoot, nullifierHash, proof } = req.body
|
||||
|
||||
try {
|
||||
const transaction = await contract.sendFeedback(feedback, merkleTreeRoot, nullifierHash, proof)
|
||||
|
||||
await transaction.wait()
|
||||
|
||||
res.status(200).end()
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
|
||||
res.status(500).end()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Contract, providers, Wallet } from "ethers"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import Feedback from "../../../contract-artifacts/Feedback.json"
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (typeof process.env.FEEDBACK_CONTRACT_ADDRESS !== "string") {
|
||||
throw new Error("Please, define FEEDBACK_CONTRACT_ADDRESS in your .env file")
|
||||
}
|
||||
|
||||
if (typeof process.env.DEFAULT_NETWORK !== "string") {
|
||||
throw new Error("Please, define DEFAULT_NETWORK in your .env file")
|
||||
}
|
||||
|
||||
if (typeof process.env.INFURA_API_KEY !== "string") {
|
||||
throw new Error("Please, define INFURA_API_KEY in your .env file")
|
||||
}
|
||||
|
||||
if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") {
|
||||
throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file")
|
||||
}
|
||||
|
||||
const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY
|
||||
const ethereumNetwork = process.env.DEFAULT_NETWORK
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS
|
||||
|
||||
const provider =
|
||||
ethereumNetwork === "localhost"
|
||||
? new providers.JsonRpcProvider()
|
||||
: new providers.InfuraProvider(ethereumNetwork, infuraApiKey)
|
||||
|
||||
const signer = new Wallet(ethereumPrivateKey, provider)
|
||||
const contract = new Contract(contractAddress, Feedback.abi, signer)
|
||||
|
||||
const { identityCommitment } = req.body
|
||||
|
||||
try {
|
||||
const transaction = await contract.joinGroup(identityCommitment)
|
||||
|
||||
await transaction.wait()
|
||||
|
||||
res.status(200).end()
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
|
||||
res.status(500).end()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import getNextConfig from "next/config"
|
||||
import { useRouter } from "next/router"
|
||||
import { useCallback, useContext, useEffect, useState } from "react"
|
||||
import Feedback from "../../contract-artifacts/Feedback.json"
|
||||
import Stepper from "../components/Stepper"
|
||||
import LogsContext from "../context/LogsContext"
|
||||
import SemaphoreContext from "../context/SemaphoreContext"
|
||||
|
||||
const { publicRuntimeConfig: env } = getNextConfig()
|
||||
|
||||
export default function GroupsPage() {
|
||||
const router = useRouter()
|
||||
const { setLogs } = useContext(LogsContext)
|
||||
const { _users, refreshUsers, addUser } = useContext(SemaphoreContext)
|
||||
const [_loading, setLoading] = useState(false)
|
||||
const [_identity, setIdentity] = useState<Identity>()
|
||||
|
||||
useEffect(() => {
|
||||
const identityString = localStorage.getItem("identity")
|
||||
|
||||
if (!identityString) {
|
||||
router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
setIdentity(new Identity(identityString))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (_users.length > 0) {
|
||||
setLogs(`${_users.length} user${_users.length > 1 ? "s" : ""} retrieved from the group 🤙🏽`)
|
||||
}
|
||||
}, [_users])
|
||||
|
||||
const joinGroup = useCallback(async () => {
|
||||
if (!_identity) {
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
setLogs(`Joining the Feedback group...`)
|
||||
|
||||
let response: any
|
||||
|
||||
if (env.OPENZEPPELIN_AUTOTASK_WEBHOOK) {
|
||||
response = await fetch(env.OPENZEPPELIN_AUTOTASK_WEBHOOK, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
abi: Feedback.abi,
|
||||
address: env.FEEDBACK_CONTRACT_ADDRESS,
|
||||
functionName: "joinGroup",
|
||||
functionParameters: [_identity.commitment.toString()]
|
||||
})
|
||||
})
|
||||
} else {
|
||||
response = await fetch("api/join", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
identityCommitment: _identity.commitment.toString()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
addUser(_identity.commitment.toString())
|
||||
|
||||
setLogs(`You joined the Feedback group event 🎉 Share your feedback anonymously!`)
|
||||
} else {
|
||||
setLogs("Some error occurred, please try again!")
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}, [_identity])
|
||||
|
||||
const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment.toString()), [_users])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Groups</h2>
|
||||
|
||||
<p>
|
||||
Semaphore{" "}
|
||||
<a
|
||||
href="https://semaphore.pse.dev/docs/guides/groups"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
>
|
||||
groups
|
||||
</a>{" "}
|
||||
are binary incremental Merkle trees in which each leaf contains an identity commitment for a user.
|
||||
Groups can be abstracted to represent events, polls, or organizations.
|
||||
</p>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<div className="text-top">
|
||||
<h3>Feedback users ({_users.length})</h3>
|
||||
<button className="button-link" onClick={refreshUsers}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button"
|
||||
onClick={joinGroup}
|
||||
disabled={_loading || !_identity || userHasJoined(_identity)}
|
||||
>
|
||||
<span>Join group</span>
|
||||
{_loading && <div className="loader"></div>}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{_users.length > 0 && (
|
||||
<div>
|
||||
{_users.map((user, i) => (
|
||||
<div key={i}>
|
||||
<p className="box box-text">{user}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<Stepper
|
||||
step={2}
|
||||
onPrevClick={() => router.push("/")}
|
||||
onNextClick={_identity && userHasJoined(_identity) ? () => router.push("/proofs") : undefined}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { useRouter } from "next/router"
|
||||
import { useCallback, useContext, useEffect, useState } from "react"
|
||||
import Stepper from "../components/Stepper"
|
||||
import LogsContext from "../context/LogsContext"
|
||||
|
||||
export default function IdentitiesPage() {
|
||||
const router = useRouter()
|
||||
const { setLogs } = useContext(LogsContext)
|
||||
const [_identity, setIdentity] = useState<Identity>()
|
||||
|
||||
useEffect(() => {
|
||||
const identityString = localStorage.getItem("identity")
|
||||
|
||||
if (identityString) {
|
||||
const identity = new Identity(identityString)
|
||||
|
||||
setIdentity(identity)
|
||||
|
||||
setLogs("Your Semaphore identity was retrieved from the browser cache 👌🏽")
|
||||
} else {
|
||||
setLogs("Create your Semaphore identity 👆🏽")
|
||||
}
|
||||
}, [])
|
||||
|
||||
const createIdentity = useCallback(async () => {
|
||||
const identity = new Identity()
|
||||
|
||||
setIdentity(identity)
|
||||
|
||||
localStorage.setItem("identity", identity.toString())
|
||||
|
||||
setLogs("Your new Semaphore identity was just created 🎉")
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="font-size: 3rem;">Identities</h2>
|
||||
|
||||
<p>
|
||||
Users interact with the protocol using a Semaphore{" "}
|
||||
<a
|
||||
href="https://semaphore.pse.dev/docs/guides/identities"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
>
|
||||
identity
|
||||
</a>{" "}
|
||||
(similar to Ethereum accounts). It contains three values:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Trapdoor: private, known only by user</li>
|
||||
<li>Nullifier: private, known only by user</li>
|
||||
<li>Commitment: public</li>
|
||||
</ol>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<div className="text-top">
|
||||
<h3>Identity</h3>
|
||||
{_identity && (
|
||||
<button className="button-link" onClick={createIdentity}>
|
||||
New
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{_identity ? (
|
||||
<div>
|
||||
<div className="box">
|
||||
<p className="box-text">Trapdoor: {_identity.trapdoor.toString()}</p>
|
||||
<p className="box-text">Nullifier: {_identity.nullifier.toString()}</p>
|
||||
<p className="box-text">Commitment: {_identity.commitment.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<button className="button" onClick={createIdentity}>
|
||||
Create identity
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<Stepper step={1} onNextClick={_identity && (() => router.push("/groups"))} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { BigNumber, utils } from "ethers"
|
||||
import getNextConfig from "next/config"
|
||||
import { useRouter } from "next/router"
|
||||
import { useCallback, useContext, useEffect, useState } from "react"
|
||||
import Feedback from "../../contract-artifacts/Feedback.json"
|
||||
import Stepper from "../components/Stepper"
|
||||
import LogsContext from "../context/LogsContext"
|
||||
import SemaphoreContext from "../context/SemaphoreContext"
|
||||
|
||||
const { publicRuntimeConfig: env } = getNextConfig()
|
||||
|
||||
export default function ProofsPage() {
|
||||
const router = useRouter()
|
||||
const { setLogs } = useContext(LogsContext)
|
||||
const { _users, _feedback, refreshFeedback, addFeedback } = useContext(SemaphoreContext)
|
||||
const [_loading, setLoading] = useState(false)
|
||||
const [_identity, setIdentity] = useState<Identity>()
|
||||
|
||||
useEffect(() => {
|
||||
const identityString = localStorage.getItem("identity")
|
||||
|
||||
if (!identityString) {
|
||||
router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
setIdentity(new Identity(identityString))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (_feedback.length > 0) {
|
||||
setLogs(`${_feedback.length} feedback retrieved from the group 🤙🏽`)
|
||||
}
|
||||
}, [_feedback])
|
||||
|
||||
const sendFeedback = useCallback(async () => {
|
||||
if (!_identity) {
|
||||
return
|
||||
}
|
||||
|
||||
const feedback = prompt("Please enter your feedback:")
|
||||
|
||||
if (feedback && _users) {
|
||||
setLoading(true)
|
||||
|
||||
setLogs(`Posting your anonymous feedback...`)
|
||||
|
||||
try {
|
||||
const group = new Group(env.GROUP_ID)
|
||||
|
||||
const signal = BigNumber.from(utils.formatBytes32String(feedback)).toString()
|
||||
|
||||
group.addMembers(_users)
|
||||
|
||||
const { proof, merkleTreeRoot, nullifierHash } = await generateProof(
|
||||
_identity,
|
||||
group,
|
||||
env.GROUP_ID,
|
||||
signal
|
||||
)
|
||||
|
||||
let response: any
|
||||
|
||||
if (env.OPENZEPPELIN_AUTOTASK_WEBHOOK) {
|
||||
response = await fetch(env.OPENZEPPELIN_AUTOTASK_WEBHOOK, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
abi: Feedback.abi,
|
||||
address: env.FEEDBACK_CONTRACT_ADDRESS,
|
||||
functionName: "sendFeedback",
|
||||
functionParameters: [signal, merkleTreeRoot, nullifierHash, proof]
|
||||
})
|
||||
})
|
||||
} else {
|
||||
response = await fetch("api/feedback", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
feedback: signal,
|
||||
merkleTreeRoot,
|
||||
nullifierHash,
|
||||
proof
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
addFeedback(feedback)
|
||||
|
||||
setLogs(`Your feedback was posted 🎉`)
|
||||
} else {
|
||||
setLogs("Some error occurred, please try again!")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
setLogs("Some error occurred, please try again!")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [_identity])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Proofs</h2>
|
||||
|
||||
<p>
|
||||
Semaphore members can anonymously{" "}
|
||||
<a
|
||||
href="https://semaphore.pse.dev/docs/guides/proofs"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
>
|
||||
prove
|
||||
</a>{" "}
|
||||
that they are part of a group and that they are generating their own signals. Signals could be anonymous
|
||||
votes, leaks, reviews, or feedback.
|
||||
</p>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<div className="text-top">
|
||||
<h3>Feedback signals ({_feedback.length})</h3>
|
||||
<button className="button-link" onClick={refreshFeedback}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button className="button" onClick={sendFeedback} disabled={_loading}>
|
||||
<span>Send Feedback</span>
|
||||
{_loading && <div className="loader"></div>}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{_feedback.length > 0 && (
|
||||
<div>
|
||||
{_feedback.map((f, i) => (
|
||||
<div key={i}>
|
||||
<p className="box box-text">{f}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
<Stepper step={3} onPrevClick={() => router.push("/groups")} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
:root {
|
||||
--slate100: #f1f5f9;
|
||||
--slate300: #cbd5e1;
|
||||
--slate400: #94a3b8;
|
||||
--slate700: #334155;
|
||||
--blue200: #bfdbfe;
|
||||
--blue600: #2563eb;
|
||||
--blue800: #1e40af;
|
||||
--blue900: #1e3a8a;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--slate700);
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
max-width: 32rem;
|
||||
|
||||
margin: auto;
|
||||
padding: 1rem;
|
||||
|
||||
min-height: calc(100vh - 3.5rem);
|
||||
}
|
||||
|
||||
.container #body {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.text-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--slate400);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.stepper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue600);
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: var(--blue800);
|
||||
width: 100%;
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--slate100);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
transition: all 200ms linear;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
opacity: 0.9;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 3.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--blue900);
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.button:disabled:hover {
|
||||
background-color: var(--blue800);
|
||||
}
|
||||
|
||||
.button-stepper {
|
||||
cursor: pointer;
|
||||
color: var(--blue900);
|
||||
font-size: 1.1rem;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.button-link {
|
||||
cursor: pointer;
|
||||
color: var(--slate700);
|
||||
font-size: 1.1rem;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.box {
|
||||
padding: 0.8rem;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--slate300);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.box-text {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--blue200);
|
||||
padding: 1rem 0;
|
||||
|
||||
height: 3.5rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
border-top: 2px solid var(--slate100);
|
||||
border-right: 2px solid transparent;
|
||||
animation: spin 1s linear infinite;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next.config.js", "next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
44
packages/cli-template-monorepo-ethers/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli-template-monorepo-ethers",
|
||||
"version": "3.14.0",
|
||||
"description": "Semaphore Hardhat + Next.js + SemaphoreEthers template.",
|
||||
"license": "Unlicense",
|
||||
"files": [
|
||||
"files.tgz",
|
||||
"scripts/",
|
||||
".editorconfig",
|
||||
".env.example",
|
||||
".eslintignore",
|
||||
".eslintrc.json",
|
||||
".prettierignore",
|
||||
".prettierrc.json",
|
||||
"tsconfig.json",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "yarn workspaces foreach -pi run dev",
|
||||
"dev:web-app": "yarn workspace monorepo-ethers-web-app dev",
|
||||
"dev:contracts": "yarn workspace monorepo-ethers-contracts dev",
|
||||
"lint": "eslint . --ext .js,.ts",
|
||||
"prettier": "prettier -c .",
|
||||
"prettier:write": "prettier -w .",
|
||||
"copy:contract-artifacts": "ts-node scripts/copy-contract-artifacts.ts",
|
||||
"prepublish": "tar -czf files.tgz .gitignore .yarn .yarnrc.yml apps"
|
||||
},
|
||||
"workspaces": [
|
||||
"apps/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-node": "^10.8.1",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as fs from "fs"
|
||||
|
||||
async function main() {
|
||||
const contractArtifactsPath = "apps/contracts/build/contracts/contracts/Feedback.sol"
|
||||
const webAppArtifactsPath = "apps/web-app/contract-artifacts"
|
||||
|
||||
await fs.promises.copyFile(`${contractArtifactsPath}/Feedback.json`, `${webAppArtifactsPath}/Feedback.json`)
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
3
packages/cli-template-monorepo-ethers/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"include": ["scripts/**/*"]
|
||||
}
|
||||
13
packages/cli-template-monorepo-subgraph/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
#root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
10
packages/cli-template-monorepo-subgraph/.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
DEFAULT_NETWORK=localhost
|
||||
INFURA_API_KEY=
|
||||
ETHEREUM_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||
FEEDBACK_CONTRACT_ADDRESS=0x5fc8d32690cc91d4c39d9d3abcbd16989f875707
|
||||
SEMAPHORE_CONTRACT_ADDRESS=0xdc64a140aa3e981100a9beca4e685f962f0cf6c9
|
||||
OPENZEPPELIN_AUTOTASK_WEBHOOK=
|
||||
GROUP_ID=42
|
||||
REPORT_GAS=false
|
||||
COINMARKETCAP_API_KEY=
|
||||
ETHERSCAN_API_KEY=
|
||||
45
packages/cli-template-monorepo-subgraph/.eslintignore
Normal file
@@ -0,0 +1,45 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# docs
|
||||
docs
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
cache
|
||||
contract-artifacts
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# The Graph
|
||||
generated
|
||||
|
||||
# Auto Generated PWA files
|
||||
**/public/sw.js
|
||||
**/public/workbox-*.js
|
||||
**/public/worker-*.js
|
||||
**/public/sw.js.map
|
||||
**/public/workbox-*.js.map
|
||||
**/public/worker-*.js.map
|
||||
36
packages/cli-template-monorepo-subgraph/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"extends": ["airbnb-base", "airbnb-typescript/base", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"project": ["./**/tsconfig.json"]
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"no-underscore-dangle": "off",
|
||||
"no-alert": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-relative-packages": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"no-bitwise": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-console": ["warn", { "allow": ["info", "warn", "error"] }],
|
||||
"@typescript-eslint/lines-between-class-members": "off",
|
||||
"no-param-reassign": "off",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "variable",
|
||||
"format": ["camelCase", "PascalCase", "UPPER_CASE", "snake_case"],
|
||||
"leadingUnderscore": "allow"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
122
packages/cli-template-monorepo-subgraph/.gitignore
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Cargo
|
||||
target
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
coverage.json
|
||||
*.lcov
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# Generate output
|
||||
dist
|
||||
build
|
||||
cache
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
next-env.d.ts
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
*.DS_Store
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.pnp.js
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# The Graph
|
||||
generated
|
||||
|
||||
# Auto Generated PWA files
|
||||
**/public/sw.js
|
||||
**/public/workbox-*.js
|
||||
**/public/worker-*.js
|
||||
**/public/sw.js.map
|
||||
**/public/workbox-*.js.map
|
||||
**/public/worker-*.js.map
|
||||
|
||||
#amplify-do-not-edit-begin
|
||||
amplify/\#current-cloud-backend
|
||||
amplify/.config/local-*
|
||||
amplify/logs
|
||||
amplify/mock-data
|
||||
amplify/mock-api-resources
|
||||
amplify/backend/amplify-meta.json
|
||||
amplify/backend/.temp
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
aws-exports.js
|
||||
awsconfiguration.json
|
||||
amplifyconfiguration.json
|
||||
amplifyconfiguration.dart
|
||||
amplify-build-config.json
|
||||
amplify-gradle-config.json
|
||||
amplifytools.xcconfig
|
||||
.secret-*
|
||||
**.sample
|
||||
#amplify-do-not-edit-end
|
||||
|
||||
# Others
|
||||
files.tgz
|
||||
45
packages/cli-template-monorepo-subgraph/.prettierignore
Normal file
@@ -0,0 +1,45 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# docs
|
||||
docs
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
cache
|
||||
contract-artifacts
|
||||
|
||||
# github
|
||||
.github/ISSUE_TEMPLATE
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
|
||||
# The Graph
|
||||
generated
|
||||
|
||||
# Auto Generated PWA files
|
||||
**/public/sw.js
|
||||
**/public/workbox-*.js
|
||||
**/public/worker-*.js
|
||||
**/public/sw.js.map
|
||||
**/public/workbox-*.js.map
|
||||
**/public/worker-*.js.map
|
||||
5
packages/cli-template-monorepo-subgraph/.prettierrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
||||
28
packages/cli-template-monorepo-subgraph/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
1
packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id
vendored
Normal file
@@ -0,0 +1 @@
|
||||
b3cadff6efb37a12712d12c2553ec703dbcaa4dd
|
||||
9
packages/cli-template-monorepo-subgraph/.yarnrc.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
checksumBehavior: update
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||
59
packages/cli-template-monorepo-subgraph/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Semaphore Hardhat + Next.js + SemaphoreEthers template
|
||||
|
||||
This project is a complete application that demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract. It also contains a frontend to play around with the contract.
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
Copy the `.env.example` file as `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
and add your environment variables or run the app in a local network.
|
||||
|
||||
### Local server
|
||||
|
||||
You can start your app locally with:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### Deploy the contract
|
||||
|
||||
1. Go to the `apps/contracts` directory and deploy your contract:
|
||||
|
||||
```bash
|
||||
yarn deploy --semaphore <semaphore-address> --group <group-id> --network arbitrum-goerli
|
||||
```
|
||||
|
||||
2. Update your `.env` file with your new contract address, the group id and the semaphore contract address.
|
||||
|
||||
3. Copy your contract artifacts from `apps/contracts/build/contracts/contracts` folder to `apps/web-app/contract-artifacts` folders manually. Or run `yarn copy:contract-artifacts` in the project root to do it automatically.
|
||||
|
||||
> **Note**
|
||||
> Check the Semaphore contract addresses [here](https://semaphore.pse.dev/docs/deployed-contracts).
|
||||
|
||||
> **Warning**
|
||||
> The group id is a number!
|
||||
|
||||
### Code quality and formatting
|
||||
|
||||
Run [ESLint](https://eslint.org/) to analyze the code and catch bugs:
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
Run [Prettier](https://prettier.io/) to check formatting rules:
|
||||
|
||||
```bash
|
||||
yarn prettier
|
||||
```
|
||||
|
||||
or to automatically format the code:
|
||||
|
||||
```bash
|
||||
yarn prettier:write
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "solhint:default"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
||||
|
||||
contract Feedback {
|
||||
ISemaphore public semaphore;
|
||||
|
||||
uint256 public groupId;
|
||||
|
||||
constructor(address semaphoreAddress, uint256 _groupId) {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment) external {
|
||||
semaphore.addMember(groupId, identityCommitment);
|
||||
}
|
||||
|
||||
function sendFeedback(
|
||||
uint256 feedback,
|
||||
uint256 merkleTreeRoot,
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import "@nomicfoundation/hardhat-chai-matchers"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomiclabs/hardhat-etherscan"
|
||||
import "@semaphore-protocol/hardhat"
|
||||
import "@typechain/hardhat"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { NetworksUserConfig } from "hardhat/types"
|
||||
import { resolve } from "path"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy"
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "../../.env") })
|
||||
|
||||
function getNetworks(): NetworksUserConfig {
|
||||
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
||||
const infuraApiKey = process.env.INFURA_API_KEY
|
||||
|
||||
return {
|
||||
goerli: {
|
||||
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
"arbitrum-goerli": {
|
||||
url: "https://goerli-rollup.arbitrum.io/rpc",
|
||||
chainId: 421613,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: "https://arb1.arbitrum.io/rpc",
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: config.solidity,
|
||||
paths: {
|
||||
sources: config.paths.contracts,
|
||||
tests: config.paths.tests,
|
||||
cache: config.paths.cache,
|
||||
artifacts: config.paths.build.contracts
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337
|
||||
},
|
||||
...getNetworks()
|
||||
},
|
||||
gasReporter: {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
},
|
||||
typechain: {
|
||||
outDir: config.paths.build.typechain,
|
||||
target: "ethers-v5"
|
||||
},
|
||||
etherscan: {
|
||||
apiKey: process.env.ETHERSCAN_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "monorepo-subgraph-contracts",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
|
||||
"compile": "hardhat compile",
|
||||
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
|
||||
"deploy": "yarn compile && hardhat deploy",
|
||||
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage",
|
||||
"typechain": "hardhat typechain",
|
||||
"lint": "solhint 'contracts/**/*.sol'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.7",
|
||||
"@semaphore-protocol/group": "3.14.0",
|
||||
"@semaphore-protocol/hardhat": "3.14.0",
|
||||
"@semaphore-protocol/identity": "3.14.0",
|
||||
"@semaphore-protocol/proof": "3.14.0",
|
||||
"@typechain/ethers-v5": "^10.0.0",
|
||||
"@typechain/hardhat": "^6.0.0",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/download": "^8.0.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"chai": "^4.2.0",
|
||||
"dotenv": "^14.3.2",
|
||||
"download": "^8.0.0",
|
||||
"ethers": "^5.0.0",
|
||||
"hardhat": "^2.8.4",
|
||||
"hardhat-gas-reporter": "^1.0.8",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.19",
|
||||
"solhint": "^3.3.6",
|
||||
"solhint-plugin-prettier": "^0.0.5",
|
||||
"solidity-coverage": "^0.7.21",
|
||||
"typechain": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/contracts": "3.14.0"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"build": {
|
||||
"snark-artifacts": "./build/snark-artifacts",
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import download from "download"
|
||||
import fs from "fs"
|
||||
import { config } from "../package.json"
|
||||
|
||||
async function main() {
|
||||
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
|
||||
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
|
||||
|
||||
if (!fs.existsSync(snarkArtifactsPath)) {
|
||||
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
|
||||
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
|
||||
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy", "Deploy a Feedback contract")
|
||||
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
|
||||
.addOptionalParam("group", "Group id", "42", types.string)
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
|
||||
if (!semaphoreAddress) {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs
|
||||
})
|
||||
|
||||
semaphoreAddress = semaphore.address
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
groupId = process.env.GROUP_ID
|
||||
}
|
||||
|
||||
const FeedbackFactory = await ethers.getContractFactory("Feedback")
|
||||
|
||||
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
|
||||
|
||||
await feedbackContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
|
||||
}
|
||||
|
||||
return feedbackContract
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import { formatBytes32String } from "ethers/lib/utils"
|
||||
import { run } from "hardhat"
|
||||
// @ts-ignore: typechain folder will be generated after contracts compilation
|
||||
import { Feedback } from "../build/typechain"
|
||||
import { config } from "../package.json"
|
||||
|
||||
describe("Feedback", () => {
|
||||
let feedbackContract: Feedback
|
||||
let semaphoreContract: string
|
||||
|
||||
const groupId = "42"
|
||||
const group = new Group(groupId)
|
||||
const users: Identity[] = []
|
||||
|
||||
before(async () => {
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs: false
|
||||
})
|
||||
|
||||
feedbackContract = await run("deploy", {
|
||||
logs: false,
|
||||
group: groupId,
|
||||
semaphore: semaphore.address
|
||||
})
|
||||
semaphoreContract = semaphore
|
||||
|
||||
users.push(new Identity())
|
||||
users.push(new Identity())
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
it("Should allow users to join the group", async () => {
|
||||
for await (const [i, user] of users.entries()) {
|
||||
const transaction = feedbackContract.joinGroup(user.commitment)
|
||||
|
||||
group.addMember(user.commitment)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "MemberAdded")
|
||||
.withArgs(groupId, i, user.commitment, group.root)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("# sendFeedback", () => {
|
||||
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
|
||||
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
|
||||
|
||||
it("Should allow users to send feedback anonymously", async () => {
|
||||
const feedback = formatBytes32String("Hello World")
|
||||
|
||||
const fullProof = await generateProof(users[1], group, groupId, feedback, {
|
||||
wasmFilePath,
|
||||
zkeyFilePath
|
||||
})
|
||||
|
||||
const transaction = feedbackContract.sendFeedback(
|
||||
feedback,
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(semaphoreContract, "ProofVerified")
|
||||
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"typeRoots": ["node_modules/@types", "types"]
|
||||
},
|
||||
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
||||
"files": ["./hardhat.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "Feedback",
|
||||
"sourceName": "contracts/Feedback.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "semaphoreAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "_groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "groupId",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "joinGroup",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "semaphore",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract ISemaphore",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "feedback",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "nullifierHash",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[8]",
|
||||
"name": "proof",
|
||||
"type": "uint256[8]"
|
||||
}
|
||||
],
|
||||
"name": "sendFeedback",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x608060405234801561001057600080fd5b506040516106e13803806106e18339818101604052810190610032919061013c565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639c1121416001546014306040518463ffffffff1660e01b81526004016100d9939291906101a5565b600060405180830381600087803b1580156100f357600080fd5b505af1158015610107573d6000803e3d6000fd5b505050505050610258565b6000815190506101218161022a565b92915050565b60008151905061013681610241565b92915050565b6000806040838503121561014f57600080fd5b600061015d85828601610112565b925050602061016e85828601610127565b9150509250929050565b610181816101dc565b82525050565b61019081610218565b82525050565b61019f8161020e565b82525050565b60006060820190506101ba6000830186610196565b6101c76020830185610187565b6101d46040830184610178565b949350505050565b60006101e7826101ee565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102238261020e565b9050919050565b610233816101dc565b811461023e57600080fd5b50565b61024a8161020e565b811461025557600080fd5b50565b61047a806102676000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
|
||||
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const fs = require("fs")
|
||||
const withPWA = require("next-pwa")
|
||||
|
||||
if (!fs.existsSync("./.env")) {
|
||||
// eslint-disable-next-line global-require
|
||||
require("dotenv").config({ path: "../../.env" })
|
||||
}
|
||||
|
||||
const nextConfig = withPWA({
|
||||
dest: "public",
|
||||
disable: process.env.NODE_ENV === "development"
|
||||
})({
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true
|
||||
},
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
env: {
|
||||
DEFAULT_NETWORK: process.env.DEFAULT_NETWORK,
|
||||
INFURA_API_KEY: process.env.INFURA_API_KEY,
|
||||
ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY,
|
||||
FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS,
|
||||
SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
DEFAULT_NETWORK: process.env.DEFAULT_NETWORK,
|
||||
FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS,
|
||||
SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS,
|
||||
OPENZEPPELIN_AUTOTASK_WEBHOOK: process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK,
|
||||
GROUP_ID: process.env.GROUP_ID
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
if (!isServer) {
|
||||
config.resolve.fallback = {
|
||||
fs: false
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "monorepo-subgraph-web-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"export": "next export",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/font": "13.0.3",
|
||||
"@semaphore-protocol/data": "3.14.0",
|
||||
"@semaphore-protocol/group": "3.14.0",
|
||||
"@semaphore-protocol/identity": "3.14.0",
|
||||
"@semaphore-protocol/proof": "3.14.0",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "18.0.8",
|
||||
"dotenv": "^16.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"next": "13.0.3",
|
||||
"next-pwa": "^5.6.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 827 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |