Compare commits

...

113 Commits

Author SHA1 Message Date
cedoor
9d24c8184a chore: v3.11.0
Former-commit-id: 478e6fa382
2023-08-21 16:24:13 +02:00
cedoor
ae359322e9 test(data): add test for identityCommitment filter
Former-commit-id: 7a07dda628
2023-08-21 16:23:10 +02:00
Cedoor
33998d90f7 Merge pull request #346 from semaphore-protocol/chore/snarkjs-version
New snarkjs version

Former-commit-id: 44acbf806f
2023-08-21 16:13:47 +02:00
Cedoor
d9de7c1033 Merge pull request #347 from semaphore-protocol/refactor/proof-exports
`MerkleProof` type to exports

Former-commit-id: ac555f59a1
2023-08-21 16:13:37 +02:00
cedoor
120f99b416 refactor(proof): add MerkleProof type to exports
re #337


Former-commit-id: 8afa314891
2023-08-21 13:24:16 +02:00
Cedoor
b95cb17135 Merge pull request #338 from wslyvh/main
New filter option to query groups based on identity commitment

Former-commit-id: 7467aac9e7
2023-08-21 13:18:51 +02:00
cedoor
507c252d36 chore(proof): update snarkjs version
re #339


Former-commit-id: 82ad9a543b
2023-08-21 13:17:33 +02:00
wslyvh
82db6a1eb7 feat: add identitycommitment as groups filter
Allow to filter on identity commitments when getting subgraph groups


Former-commit-id: 369257d40b
2023-08-14 10:05:19 +02:00
cedoor
c60ab38882 docs: add new project to readme file
Former-commit-id: 7d26d4e66c
2023-08-02 12:00:53 +02:00
Cedoor
cba5940f36 Merge pull request #336 from semaphore-protocol/docs/add-project
Add StealthComms to the projects file

Former-commit-id: e5bcd18045
2023-08-02 11:57:42 +02:00
vplasencia
110e3de844 docs: change tagline
Former-commit-id: 763663ca3e
2023-08-02 10:47:09 +02:00
vplasencia
f60e85335d docs: fix typo
Former-commit-id: a786f10ba5
2023-08-02 10:40:06 +02:00
vplasencia
ef4cef65cd docs: fix typo
Former-commit-id: 44177ce5b7
2023-08-02 10:35:07 +02:00
vplasencia
f69534683d docs: add a new project to the projects file
Former-commit-id: 8552640cd0
2023-08-02 10:25:25 +02:00
cedoor
4d849b48c2 chore: v3.10.1
Former-commit-id: 41181c63d2
2023-05-17 10:56:59 +01:00
cedoor
531e16e0d7 fix(group): make members bigint types
Former-commit-id: d1d309ac3a
2023-05-17 10:46:40 +01:00
Cedoor
f2575f2bec Merge pull request #324 from semaphore-protocol/feat/is-member
New function to check if a member exists

Former-commit-id: e783465dc2
2023-05-15 15:50:18 +01:00
cedoor
3bfc7ce2bc style(data): format code with prettier
Former-commit-id: 2b430dace0
2023-05-15 15:43:50 +01:00
cedoor
ef88c9c84c feat(data): create function to check if a member exists
re #321


Former-commit-id: 9d7076acc7
2023-05-15 15:35:53 +01:00
Cedoor
6580b6620f Merge pull request #322 from semaphore-protocol/feat/init-members
New class parameter to add members

Former-commit-id: 5915ea082b
2023-05-15 12:59:46 +01:00
cedoor
42d33e2af7 feat(group): add new class parameter to add members
re #319


Former-commit-id: ed015ccc83
2023-05-15 12:46:44 +01:00
Cedoor
594c612318 Merge pull request #316 from semaphore-protocol/docs/update-readme-data-package
Update readme file in data package

Former-commit-id: 0ee1fa2b72
2023-05-02 09:46:50 +01:00
vplasencia
fbfa92023d docs(data): update readme file
Former-commit-id: 594daba6cc
2023-05-01 20:58:00 +02:00
cedoor
caed0aecc1 test(data): add test for new function
Former-commit-id: 0cb0ef3514
2023-04-28 12:32:30 +01:00
cedoor
7bcc44c645 chore: v3.9.0
Former-commit-id: 900da9cef5
2023-04-28 12:27:48 +01:00
Cedoor
0ac390eaf0 Merge pull request #315 from semaphore-protocol/feat/subgraph
Sepolia subgraph & supported networks

Former-commit-id: f50a16054b
2023-04-28 11:33:57 +01:00
cedoor
5db4c61c28 feat(data): provide function to get supported networks
re #314


Former-commit-id: 5e033556fb
2023-04-27 16:04:20 +01:00
cedoor
37dc17d880 feat(data): add sepolia subgraph support
Former-commit-id: 92a6a7a354
2023-04-27 16:03:54 +01:00
cedoor
a04057f861 chore: v3.8.0
Former-commit-id: 736bddbd83
2023-04-26 15:34:48 +01:00
Cedoor
29e8f5852f Merge pull request #312 from semaphore-protocol/feat/nullifier-hash
New function to calculate nullifier hash

Former-commit-id: 946ac8333c
2023-04-26 15:20:20 +01:00
Cedoor
26c76f453e Merge pull request #313 from 0xdeenz/main
chore: add Block Qualified to list of projects
Former-commit-id: 37951ff29e
2023-04-26 12:43:24 +01:00
cedoor
9a1a1e7853 docs: update package docs links
Former-commit-id: 8ff372ef35
2023-04-26 12:37:09 +01:00
cedoor
f583caa243 ci(github): add semaphore js cname
Former-commit-id: 9a24d36c0b
2023-04-26 12:25:20 +01:00
0xdeenz
b399b923ab chore: add Block Qualified to list of projects
Former-commit-id: dc7e0a1d0b
2023-04-25 20:04:37 +02:00
Cedoor
65e5d29dcd docs(proof): update README.md
Former-commit-id: 3f1432c8b9
2023-04-22 17:28:06 +01:00
cedoor
ba9782139e feat(proof): add function to calculate nullifier hash
Former-commit-id: 6cc2b6af1c
2023-04-22 17:23:52 +01:00
Cedoor
edef368976 Merge pull request #311 from semaphore-protocol/ref/cli
Update get-proofs description

Former-commit-id: 10024bced9
2023-04-22 10:42:14 +01:00
vplasencia
77fddc9b16 docs(cli): update the description of get-proofs command in the readme file
Former-commit-id: d151998013
2023-04-22 09:26:21 +02:00
vplasencia
69b2b8fd7b refactor(cli): update get-proofs description
Former-commit-id: eb63447166
2023-04-22 09:19:54 +02:00
cedoor
aba7d51f86 chore: v3.7.0
Former-commit-id: 22f33a8f26
2023-04-20 11:28:32 +01:00
Cedoor
32d1b47964 Merge pull request #310 from semaphore-protocol/docs/cli
Update the Readme file to add the new CLI commands

Former-commit-id: faf7ff9c18
2023-04-20 11:20:02 +01:00
vplasencia
cb3ff0fcb8 docs(cli): update the readme file to add the new commands
Former-commit-id: e7ceb37503
2023-04-20 10:15:51 +02:00
Cedoor
4d4338d3bc Merge pull request #309 from semaphore-protocol/chore/community-projects
Semaphore projects

Former-commit-id: f0574c4c40
2023-04-19 12:21:20 +01:00
cedoor
536410457e chore: add some projects
Former-commit-id: 3ee4cebd35
2023-04-19 12:08:58 +01:00
Cedoor
7469086a2a docs: add section for semaphore projects
Former-commit-id: 37bbdb4dc0
2023-04-19 12:06:29 +01:00
cedoor
3496aa6478 chore: update issue template to add projects
Former-commit-id: 10909c63c4
2023-04-19 11:28:16 +01:00
cedoor
60d5110905 chore: create json for community projects
Former-commit-id: b98bdb3a1d
2023-04-19 11:27:49 +01:00
Cedoor
0da64796ea Merge pull request #306 from vimwitch/bump-poseidon-lite
chore: bump poseidon-lite minor version
Former-commit-id: 617bb364ae
2023-04-19 10:43:56 +02:00
Chance
41ab36d24c chore: bump poseidon-lite minor version
Former-commit-id: 9f9ec7054b
2023-04-19 00:47:13 -05:00
vplasencia
6bcd77fe5b chore: vv3.6.0
Former-commit-id: c52f0036da
2023-04-11 19:49:21 +02:00
vplasencia
4a221a43c7 refactor(cli): copy the contents of the .env.example file to a new .env file
Former-commit-id: 74c2ed1b3a
2023-04-11 14:01:28 +02:00
vplasencia
37465288a7 refactor(cli): set the monorepo-ethers template as default when creating a project
Former-commit-id: 27ba252565
2023-04-11 13:48:59 +02:00
vplasencia
c38b3c5255 chore: v3.6.0-beta.4
Former-commit-id: aabad74de7
2023-04-11 11:43:15 +02:00
vplasencia
dba7a6ca85 chore: v3.6.0-beta.3
Former-commit-id: e17737d894
2023-04-07 19:36:36 +02:00
vplasencia
468a3314e3 fix(cli): update package.json file in monorepos with correct package names
Former-commit-id: c4f3565546
2023-04-07 19:10:11 +02:00
vplasencia
a80b1136cd chore: v3.6.0-beta.2
Former-commit-id: fe8a48be0d
2023-04-07 17:37:11 +02:00
vplasencia
44328f1893 chore(cli): create a script to remove unnecessary files in monorepo templates
Former-commit-id: 02658c0780
2023-04-07 17:28:18 +02:00
vplasencia
a0c1c27c35 refactor(cli): add the next-env.d.ts file to gitignore
Former-commit-id: 02d1395828
2023-04-07 14:12:53 +02:00
vplasencia
f6be176df9 chore: v3.6.0-beta.1
Former-commit-id: 6b9c9c1f28
2023-04-07 12:02:14 +02:00
vplasencia
1a2eb3e18d chore(cli): decompress and then remove the files.tgz file after downloading a template
Former-commit-id: 2467291010
2023-04-07 09:15:52 +02:00
vplasencia
44b25995e0 fix(cli): add gitignore file
Former-commit-id: bd897a6d97
2023-04-06 19:58:23 +02:00
cedoor
f1b3368529 fix(data): check if groups exist before adding members
Former-commit-id: 6e453ff32a
2023-04-06 18:36:05 +01:00
cedoor
5c64b09f51 chore(cli): create prepublish script to compress files
Former-commit-id: 206106ad66
2023-04-06 17:38:56 +01:00
vplasencia
fd08c3f2e7 chore: v3.6.0-beta.0
Former-commit-id: 4996ec1d9f
2023-04-06 15:28:09 +02:00
vplasencia
01fa043b4d chore: add files field in the package.json file in monorepo-ethers and monorepo-subgraph templates
Former-commit-id: 6f381019ff
2023-04-06 14:35:42 +02:00
vplasencia
c10b57e3cf fix(cli): add solhint file in monorepo-ethers and monorepo-subgraph templates
Former-commit-id: 7d4871f858
2023-04-06 14:19:59 +02:00
vplasencia
9a7e053f64 chore: v3.6.0-1
Former-commit-id: 83762718e9
2023-04-05 18:12:31 +02:00
vplasencia
2e6b2d678a chore: remove private from package.json file in templates
Former-commit-id: 36f9b56241
2023-04-05 18:09:04 +02:00
Vivian Plasencia
cf1c039545 Merge pull request #304 from semaphore-protocol/fix/proof-event
Correct `ProofVerified` event parameters

Former-commit-id: 4ec0ba5baa
2023-04-05 17:46:34 +02:00
Vivian Plasencia
0a496e5375 Merge pull request #303 from semaphore-protocol/milestone/semaphore-cli-templates
Add new templates and commands in the CLI

Former-commit-id: 81435b1b7b
2023-04-05 17:45:47 +02:00
cedoor
ff6835d1db chore(data): update subgraph url and contract addresses
re #304


Former-commit-id: db2820e687
2023-04-05 16:04:48 +01:00
cedoor
b350cd4b66 style(contracts): format code with prettier
Former-commit-id: 41a88132c4
2023-04-05 12:37:58 +01:00
cedoor
f4508c4db5 chore(contracts): fresh semaphore contracts deployment
re #298


Former-commit-id: 8a9c42bace
2023-04-05 12:35:11 +01:00
cedoor
2df42d49c0 fix(contracts): set correct ProofVerifier event parameters
fix #297


Former-commit-id: 84813c519a
2023-04-05 11:20:16 +01:00
cedoor
fea6b8446c chore(cli): update monorepo templates semaphore deps
Former-commit-id: 045a2a2c54
2023-04-04 17:07:56 +01:00
cedoor
322051bd3a chore(cli): make monorepo cli templates private
Former-commit-id: 0a3654bfbe
2023-04-04 16:58:32 +01:00
cedoor
6ae1249618 fix: limit some npm scripts to public packages
Former-commit-id: 94bbf40e96
2023-04-04 16:57:56 +01:00
cedoor
8528e9e758 refactor(cli): update template apps' names
Former-commit-id: 58de92a335
2023-04-04 16:36:19 +01:00
vplasencia
9bea6143a6 refactor(cli): change cli template names
Former-commit-id: 84c1dfa486
2023-04-04 16:23:34 +01:00
vplasencia
3b59bf6117 refactor(cli-template-monorepo-subgraph): rename cli hardhat-nextjs-semaphoresubgraph template
Former-commit-id: ee6e1c156d
2023-04-04 16:23:34 +01:00
vplasencia
cecdcc3fb1 refactor(cli-template-monorepo-ethers): rename cli hardhat-nextjs-semaphoreethers template package
Former-commit-id: 1c0d119e60
2023-04-04 16:23:34 +01:00
vplasencia
f57c2b4397 refactor(cli-template-contracts-hardhat): rename the cli hardhat template package
Former-commit-id: e38b040641
2023-04-04 16:23:26 +01:00
vplasencia
47f3651bc5 feat(cli): add inquirer to select a template when creating a project
Former-commit-id: 4d1a637093
2023-04-04 16:22:47 +01:00
vplasencia
8006983643 refactor: ignore .next and public folders
Former-commit-id: 6afb098709
2023-04-04 16:22:22 +01:00
vplasencia
3a7f18652f style: format code with prettier
Former-commit-id: 8475666016
2023-04-04 16:22:22 +01:00
vplasencia
c7a4cf7121 refactor(cli-template-monorepo-subgraph): add yarn.lock file
Former-commit-id: b363ce3955
2023-04-04 16:22:10 +01:00
vplasencia
66aa482b07 refactor(cli-template-monorepo-ethers): remove unused code
Former-commit-id: fcaac9f2e3
2023-04-04 16:20:47 +01:00
vplasencia
b89ed0ebb7 refactor(cli-template-hardhat-nextjs-semaphoresubgraph): remove unused import statement
Former-commit-id: 5dbbdd1b2c
2023-04-04 16:20:19 +01:00
vplasencia
4cf00f33d0 refactor(cli-template-hardhat-nextjs-semaphoresubgraph): remove unused code
Former-commit-id: e5cf466a5f
2023-04-04 16:20:19 +01:00
vplasencia
a3309ab707 feat(cli-template-hardhat-nextjs-semaphoresubgraph): add hardhat-nextjs-semaphoresubgraph template
Former-commit-id: 58310b5e67
2023-04-04 16:20:19 +01:00
vplasencia
30449c4fe7 refactor(cli-template-hardhat-nextjs-semaphoreethers): update package names
Former-commit-id: eb997a8488
2023-04-04 16:20:19 +01:00
vplasencia
6b1f1a8c31 refactor(cli-template-hardhat-nextjs-semaphoreethers): add access public in package.json file
Former-commit-id: abfff13099
2023-04-04 16:20:19 +01:00
vplasencia
70c53b6203 feat(cli-template-hardhat-nextjs-semaphoreethers): add the hardhat-nextjs-semaphoreethers template
Former-commit-id: 53d878ce47
2023-04-04 16:20:19 +01:00
vplasencia
3313be7f38 style(cli): add total of elements in the output of get-groups get-members and get-proofs commands
Former-commit-id: 1b2f6b89fa
2023-04-04 16:20:19 +01:00
vplasencia
240fee73b4 style(cli): change output text of get-proofs command
Former-commit-id: 30f82d917b
2023-04-04 16:20:19 +01:00
vplasencia
7e85c1ddda fix(cli-template-hardhat): set the correct env file path
Former-commit-id: d9b0afbf31
2023-04-04 16:20:19 +01:00
vplasencia
18d724b166 perf(cli): optimize get-proofs command
Former-commit-id: 0b060f97ec
2023-04-04 16:20:19 +01:00
vplasencia
f97a233798 perf(cli): optimize get-members command
Former-commit-id: bb0b58e3f1
2023-04-04 16:20:19 +01:00
vplasencia
e146f3e84e refactor(cli): organize get group ids logic
Former-commit-id: 93e4a5d00b
2023-04-04 16:20:17 +01:00
cedoor
81fba57259 chore(cli): add types packages
re #189


Former-commit-id: 150a4c236b
2023-04-04 16:18:43 +01:00
vplasencia
07515c5068 feat(cli): add checks for template integrity
Former-commit-id: 14e39a101c
2023-04-04 16:18:40 +01:00
vplasencia
ad812f778b refactor(cli): organize inquirer prompts
Former-commit-id: d8ab9092ea
2023-04-04 16:18:00 +01:00
vplasencia
70727203fd feat(cli): split the get-group command to make it easier to query group data
Former-commit-id: 0cbbd9b28f
2023-04-04 16:18:00 +01:00
cedoor
b93acc884b chore: v3.5.0
Former-commit-id: 64a2e010f1
2023-03-31 13:14:48 +01:00
Cedoor
c9e565a445 Merge pull request #295 from semaphore-protocol/feat/identity-getter
New identity secret attribute and getter

Former-commit-id: 2af0af9a22
2023-03-31 12:53:30 +01:00
cedoor
6cea8c5dcc feat(identity): add new identity secret attribute and getter
re #294


Former-commit-id: 99ea3a15bc
2023-03-31 12:20:49 +01:00
Cedoor
5bd7cd93f7 Merge pull request #293 from semaphore-protocol/fix/semaphore-ethers
Correct nullifier hash parameter

Former-commit-id: 1118452cd4
2023-03-28 14:00:04 +01:00
cedoor
da1b4f6d8f fix(data): set correct nullifier hash parameter
Former-commit-id: d36a79e7ab
2023-03-28 12:52:46 +01:00
Cedoor
124f627d39 Merge pull request #292 from mmqxyz/patch-1
docs: fix small typos in Pairing.sol
Former-commit-id: 53963f7cd4
2023-03-28 11:01:50 +01:00
mmqxyz
199dca2a3b fix small typos in Pairing.sol
Former-commit-id: 9aa18b2fff
2023-03-28 11:32:29 +02:00
Cedoor
51accfc939 Merge pull request #291 from semaphore-protocol/docs/heyauthn-readme
Add new author

Former-commit-id: c77451857c
2023-03-24 10:16:47 +00:00
vplasencia
1696294881 style(heyauthn): format code with prettier
Former-commit-id: 220b34dade
2023-03-24 09:39:29 +01:00
vplasencia
c85b758120 docs(heyauthn): add new author
Former-commit-id: a31259011d
2023-03-24 09:28:38 +01:00
158 changed files with 4513 additions and 372 deletions

View File

@@ -13,6 +13,7 @@ A brief description of your project. In what way have you used Semaphore?
**Other info**
- Name
- Tagline
- Icon
**Links**

View File

@@ -41,5 +41,6 @@ jobs:
with:
build_dir: docs
jekyll: false
fqdn: js.semaphore.appliedzkp.org
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -41,3 +41,7 @@ yarn-error.log*
# other
snark-artifacts
# Next.js
.next/
public

150
README.md
View File

@@ -63,8 +63,6 @@
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).
---
## 📦 Packages
<table>
@@ -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.appliedzkp.org/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.appliedzkp.org/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.appliedzkp.org/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.appliedzkp.org/data">
(docs)
</a>
</td>
@@ -222,7 +220,7 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
<a href="/packages/heyauthn">
@semaphore-protocol/heyauthn
</a>
<a href="https://semaphore-protocol.github.io/semaphore/heyauthn">
<a href="https://js.semaphore.appliedzkp.org/heyauthn">
(docs)
</a>
</td>
@@ -240,7 +238,145 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
</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://explorer.semaphore.appliedzkp.org">
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.appliedzkp.org/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.appliedzkp.org/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

View File

@@ -7,18 +7,19 @@
"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": "yarn workspaces foreach --no-private run docs",
"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",

View File

@@ -7,4 +7,4 @@ typechain-types
# Hardhat files
cache
artifacts
artifacts

View File

@@ -6,12 +6,11 @@ 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") })
dotenvConfig()
function getNetworks(): NetworksUserConfig {
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {

View File

@@ -1,18 +1,17 @@
{
"name": "@semaphore-protocol/cli-template-hardhat",
"version": "3.4.0",
"name": "@semaphore-protocol/cli-template-contracts-hardhat",
"version": "3.11.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": {
@@ -26,7 +25,8 @@
"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"
"typechain": "hardhat typechain",
"prepublish": "tar -czf files.tgz .gitignore"
},
"devDependencies": {
"@ethersproject/abi": "^5.4.7",
@@ -36,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.4.0",
"@semaphore-protocol/hardhat": "3.4.0",
"@semaphore-protocol/identity": "3.4.0",
"@semaphore-protocol/proof": "3.4.0",
"@semaphore-protocol/group": "3.11.0",
"@semaphore-protocol/hardhat": "3.11.0",
"@semaphore-protocol/identity": "3.11.0",
"@semaphore-protocol/proof": "3.11.0",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/chai": "^4.2.0",
@@ -58,7 +58,7 @@
"typescript": ">=4.5.0"
},
"dependencies": {
"@semaphore-protocol/contracts": "3.4.0"
"@semaphore-protocol/contracts": "3.11.0"
},
"config": {
"solidity": {

View 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

View 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=

View 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

View 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"
}
]
}
}

View 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

View 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

View File

@@ -0,0 +1,5 @@
{
"semi": false,
"arrowParens": "always",
"trailingComma": "none"
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
b3cadff6efb37a12712d12c2553ec703dbcaa4dd

View 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

View 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.appliedzkp.org/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
```

View File

@@ -0,0 +1,3 @@
{
"extends": "solhint:default"
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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.11.0",
"@semaphore-protocol/hardhat": "3.11.0",
"@semaphore-protocol/identity": "3.11.0",
"@semaphore-protocol/proof": "3.11.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.11.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"
}
}
}
}

View File

@@ -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)
})

View File

@@ -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
})

View 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)
})
})
})

View 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"]
}

View File

@@ -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": {}
}

View File

@@ -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

View File

@@ -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.11.0",
"@semaphore-protocol/group": "3.11.0",
"@semaphore-protocol/identity": "3.11.0",
"@semaphore-protocol/proof": "3.11.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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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"
}
]
}

View File

@@ -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>
)
}

View File

@@ -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
})

View File

@@ -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: () => {}
})

View File

@@ -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
}
}

View File

@@ -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>
)
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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.appliedzkp.org/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}
/>
</>
)
}

View File

@@ -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.appliedzkp.org/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"))} />
</>
)
}

View File

@@ -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.appliedzkp.org/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")} />
</>
)
}

View File

@@ -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);
}
}

View File

@@ -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"]
}

View File

@@ -0,0 +1,44 @@
{
"name": "@semaphore-protocol/cli-template-monorepo-ethers",
"version": "3.11.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"
}
}

View File

@@ -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)
})

View File

@@ -0,0 +1,3 @@
{
"include": ["scripts/**/*"]
}

View 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

View 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=

View 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

View 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"
}
]
}
}

View 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

View 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

View File

@@ -0,0 +1,5 @@
{
"semi": false,
"arrowParens": "always",
"trailingComma": "none"
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
b3cadff6efb37a12712d12c2553ec703dbcaa4dd

View 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

View 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.appliedzkp.org/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
```

View File

@@ -0,0 +1,3 @@
{
"extends": "solhint:default"
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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.11.0",
"@semaphore-protocol/hardhat": "3.11.0",
"@semaphore-protocol/identity": "3.11.0",
"@semaphore-protocol/proof": "3.11.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.11.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"
}
}
}
}

View File

@@ -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)
})

View File

@@ -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
})

View File

@@ -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)
})
})
})

View 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"]
}

View File

@@ -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": {}
}

View File

@@ -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

View File

@@ -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.11.0",
"@semaphore-protocol/group": "3.11.0",
"@semaphore-protocol/identity": "3.11.0",
"@semaphore-protocol/proof": "3.11.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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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"
}
]
}

View File

@@ -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>
)
}

View File

@@ -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
})

View File

@@ -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: () => {}
})

View File

@@ -0,0 +1,59 @@
import { SemaphoreSubgraph } 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 SemaphoreSubgraph(ethereumNetwork)
const group = await semaphore.getGroup(env.GROUP_ID, { members: true })
setUsers(group.members!)
}, [])
const addUser = useCallback(
(user: any) => {
setUsers([..._users, user])
},
[_users]
)
const refreshFeedback = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreSubgraph(ethereumNetwork)
const group = await semaphore.getGroup(env.GROUP_ID, {
verifiedProofs: true
})
setFeedback(
group.verifiedProofs!.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
}
}

View File

@@ -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>
)
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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.appliedzkp.org/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}
/>
</>
)
}

Some files were not shown because too many files have changed in this diff Show More