Compare commits

...

47 Commits

Author SHA1 Message Date
cedoor
bada8b8792 chore: v4.6.0 2024-11-25 13:09:50 +07:00
Cedoor
e93b19c566 chore(contracts): deploy contracts (#892)
* chore(contracts): deploy contracts

* chore(subgraph): deploy new subgraphs
2024-11-25 13:08:31 +07:00
leopardracer
862f715d05 fix: typos in documentation files (#898)
* Update usage.md

* Update glossary.md
2024-11-25 12:42:49 +07:00
Dmitry
3ed6c38587 Fixed closing tag issue in Update README.md (#894)
In the table section, there is a minor mistake in the closing tag. Instead of using </tbody>, it should be </tbody>. This is a formatting mistake.
2024-11-20 15:53:14 +07:00
Cedoor
7e183f3d2c refactor(contracts): support version range (#891)
re #888
2024-10-31 13:19:05 +00:00
Jimmy Chu
901d0954c0 refactor(contracts): specify gas cost in SemaphoreVerifier precompile calls (#883)
* chore(contracts): remove sub() from gas() in staticcall at SemaphoreVerifier

re #871

* refactor(contracts): using fixed gas cost on precompile calls

re #871

* refactor(contracts): increease fixed gas cost with safety buffer
2024-10-31 12:35:19 +00:00
Erick Vasquez
caebc1a4ab feat: add veilescrow to projects section (#890) 2024-10-30 18:46:38 +01:00
Vivian Plasencia
9b419f0c78 docs: add benchmarks page (#889)
* docs(docs): add benchmarks page to the docs

re #815

* chore(docs): update the docusaurus version
2024-10-30 10:54:13 +01:00
Vivian Plasencia
c67b888eb6 chore: v4.5.0 2024-10-28 20:23:40 +01:00
Vivian Plasencia
376cd11808 docs(identity): add export import identity to the docs (#887)
re #872
2024-10-28 13:29:35 +01:00
Vivian Plasencia
58132a9478 feat(identity): export point and signature types (#886)
* feat(identity): export point and signature types

re #885

* docs(identity): add the generate commitment function to the readme file
2024-10-28 13:29:03 +01:00
Vivian Plasencia
fadcf19206 chore: v4.4.2 2024-10-23 22:42:39 +02:00
Shikhar Singh
3be17268ab feat: remove prepublish script when creating template with Semaphore CLI (#882)
* feat(cli): remove @semaphore-protocol/cli prepublish script

The idea is to remove the prepublish script from the scripts object of the package.json file of
every cli template when the template is downloaded using the CLI.

BREAKING CHANGE: n

* refactor(cli): add comment

* refactor(cli): create seperate file for removePrePublishScript function

* refactor(cli): using updatedPackageJsonContent var instead of calling readFileSync again
2024-10-23 22:07:48 +02:00
Vivian Plasencia
82cdc60af6 fix: update shebang lines in the ts scripts (#881)
re #880
2024-10-23 10:50:46 +01:00
sripwoud
6c9ede6931 ci: install soldeer in release workflow (#867)
* ci: install `soldeer` in release workflow

* ci: setup soldeer login file (#868)

* chore: trigger release workflow

* chore: format

* ci: set `soldeer` credentials in github workspace

* chore: remove commentouts

* chore: add back tag trigger
2024-10-22 09:20:34 +01:00
cedoor
e6576cdc47 chore: v4.4.1 2024-10-21 12:41:11 +01:00
cedoor
c07dc88490 style: format code with prettier 2024-10-21 12:02:54 +01:00
Yago Pajariño
67060dd412 Update cli monorepo ethers and subgraph web app UI (#841)
* refactor(cli-template-monorepo-ethers): update cli-template-monorepo-ethers web app UI

re #836

* refactor(cli-template-monorepo-subgraph): update cli-template-monorepo-subgraph web app UI

re #836

* refactor(cli-template-monorepo-subgraph): rename subgraph web app on package.json

re #836

* refactor: update yarn.lock

re #836

* chore: fix indentation error on yarn.lock

re 836

* chore: update cli monorepo package.json

re #836

* chore(cli-template-monorepo-ethers): remove .env values

* chore(cli-template-monorepo-subgraph): change .env values

* chore(cli-template-monorepo-ethers): update .gitignore file

* chore(cli-template-monorepo-subgraph): update .gitignore file

* style(cli-template-monorepo-ethers): update globals.css

* refactor(cli-template-monorepo-ethers): replace chakra with plain html+css on cli-ethers page.tsx

* style(cli-template-monorepo-ethers): import globals css in layout

* style(cli-template-monorepo-ethers): remove chakra ui elements

* refactor(cli-template-monorepo-ethers): remove chakra ui from layout

* refactor(cli-template-monorepo-ethers): remove chakra ui from PageContainer, Stepper

* style(cli-template-monorepo-ethers): add font link

* chore(cli-template-monorepo-ethers): remove unused groups page

* style(cli-template-monorepo-ethers): remove chakra ui from group page

* style(cli-template-monorepo-ethers): remove chakra ui from proof page

* style(cli-template-monorepo-ethers): update css and add class names

* style(cli-template-monorepo-ethers): add css declarations

* style(cli-template-monorepo-ethers): add github icon

* style(cli-template-monorepo-ethers): add left arrow on stepper

* style(cli-template-monorepo-ethers): remove chakra from proofs page

* style(cli-template-monorepo-ethers): minor style improvements

* style(cli-template-monorepo-ethers): remove chakra libraries from package.json

* refactor(cli-template-monorepo-ethers): update yarn.lock file after package.json modified

* style(cli-template-monorepo-ethers): fix deepscan issues

* chore(cli-template-monorepo-ethers): remove unused packages and update dotenv version

* style(cli-template-monorepo-ethers): update stylesheet

* chore(cli-template-monorepo-ethers): update github logo

* chore(cli-template-monorepo-ethers): add loader to join group button

* chore(cli-template-monorepo-subgraph): update packages

* style(cli-template-monorepo-subgraph): update stylesheet

* style(cli-template-monorepo-subgraph): update user interface

* chore: update yarn.lock

* style: change styling and add feedback link

* chore(cli-template-monorepo-subgraph): remove unused styles and icons

* chore: remove unused context logs

* chore(cli-template-monorepo-subgraph): change data source subgraph web app

* chore: change repo url

* chore: remove unused hook useSemaphore
2024-10-21 11:58:05 +01:00
Cedoor
a4e540d8f4 chore(website): update roadmap (#878) 2024-10-18 13:03:16 +01:00
Jimmy Chu
79976f33cb Split SemaphoreVerifier key points into a separate library (#875)
* refactor(scripts): added a script to convert bigint to hex

* refactor(contracts): split SemaphoreVerifier verification key points into a separate library (#330)

* chore(contracts): yarn format & lint

* refactor(contracts): update SemaphoreVerifierKeyPts library to be internal

re #330

* chore(contracts): update spacing

* chore(contracts): updated per peer review and added invariant check for VerifierKeyPts lib

* chore(contracts): update invariant check to revert with custom error
2024-10-17 14:21:41 +01:00
cedoor
822530f2cf chore: v4.4.0 2024-10-17 14:19:38 +01:00
Vivian Plasencia
b3f862979d feat(identity): add generate commitment function (#877)
This static method is particularly useful after signature verification, as it allows retrieval of
the corresponding commitment associated with the public key.

re #873
2024-10-17 13:28:49 +02:00
cedoor
06e11d5c34 chore: v4.3.1 2024-10-16 12:20:26 +01:00
cedoor
88d4470b11 ci(circuits): add sudo before running wget command 2024-10-16 12:16:22 +01:00
Vivian Plasencia
bb1c6984b2 ci: add -O flag to the wget command 2024-10-16 13:04:32 +02:00
cedoor
5841327477 ci(circuits): update circom url 2024-10-16 11:52:02 +01:00
cedoor
eab3ab30c6 ci(circuits): update circom url 2024-10-16 11:48:30 +01:00
Cedoor
cd6dc38f9e ci(circuits): set static version of circom (#874)
* fix(circuits): add override for circom_runtime

* chore(circuits): use yarn syntax for overrides

* ci(circuits): set static version of circom
2024-10-16 11:05:07 +01:00
cedoor
3cb1b5757d revert(circuits): set previous circomkit version as static 2024-10-15 11:30:11 +01:00
cedoor
a8db8393c1 chore(circuits): update circomkit config 2024-10-15 11:24:55 +01:00
cedoor
391b571c31 chore(circuits): update circomkit dep version 2024-10-15 11:21:04 +01:00
cedoor
4c2769099f chore(circuits): update dev dependencies 2024-10-15 11:05:08 +01:00
Cedoor
29cabb49d6 build: move poseidon-lite to dependencies (#870) 2024-10-15 10:58:51 +01:00
cedoor
48b0694586 chore: v4.3.0 2024-10-03 12:46:01 +01:00
Cedoor
28684fdb1a Chore/mainnet deployments (#869)
* chore(contracts): deploy semaphore contracts on base

re #865

* chore(contracts): deploy semaphore contracts on linea

re #865

* refactor(data): add types for new networks

re #865
2024-10-03 12:42:36 +01:00
Cedoor
49a16635d8 chore(website): update roadmap.json (#866) 2024-10-01 19:04:11 +01:00
cedoor
4bfe6d8791 chore: v4.2.0 2024-10-01 18:48:37 +01:00
Cedoor
a3d97795fe Chore/new testnets (#864)
* chore(contracts): deploy contracts on base sepolia

re #863

* chore(contracts): deploy contracts on linea sepolia

re #863
2024-10-01 18:36:55 +01:00
cedoor
4c8d297902 chore: v4.1.0 2024-10-01 11:35:27 +01:00
Cedoor
fdbadf5b71 Chore/mainnet deployments (#862)
* chore(contracts): deploy contracts on arbitrum

re #859

* chore(contracts): deploy contracts on polygon

re #859

* chore(contracts): deploy contracts on optimism

re #859
2024-10-01 11:31:29 +01:00
Vivian Plasencia
4496eee137 docs(website): update events (#857) 2024-09-20 17:53:55 +02:00
Cedoor
215a12e25c docs(website): add new project (#856)
re #855
2024-09-20 16:24:23 +01:00
Cedoor
87331114c1 chore: replace discord link with telegram (#853) 2024-09-03 12:49:31 +02:00
sripwoud
0d41562dad chore: publish contracts to soldeer (#820)
* chore: define separate version.ts script

* chore: include publishing to soldeer in script

* fix(contracts): push to soldeer

re 800

* ci: fetch latest contract version from soldeer

* refactor: do not use execa in scripts

* fix: do not exit process in script

* chore: remove comments

* refactor: use shebang in ts scripts

* chore(contracts): add soldeerignore

* chore: inherit stdio in scripts

* chore: remove dry run flag

* chore: uncomment

* chore: fix typo in comment
2024-08-28 16:26:48 +02:00
Elias Rad
c435712656 docs fix spelling issues (#852)
* Update README.md

* Update contract-api.md

* Update howitworks.md

* Update libsemaphore.md
2024-08-28 16:26:31 +02:00
Vivian Plasencia
7d48311474 chore: v4.0.3 2024-08-13 09:53:22 +02:00
csiejimmyliu
5ef73e2819 Semaphore Identity example code bug fix (Fix #848) (#849)
* Semaphore identity example code bug fix

* Receive suggestion for consistency

---------

Co-authored-by: weipooppys93030 <55434365+weipooppys93030@users.noreply.github.com>
2024-08-12 23:29:04 +02:00
151 changed files with 4851 additions and 3952 deletions

View File

@@ -26,5 +26,13 @@
"no-console": ["warn", { "allow": ["info", "warn", "error"] }],
"@typescript-eslint/lines-between-class-members": "off",
"no-param-reassign": "off"
}
},
"overrides": [
{
"files": ["./scripts/*"],
"rules": {
"no-console": "off"
}
}
]
}

View File

@@ -83,7 +83,7 @@ jobs:
sudo apt-get update && sudo apt-get install -y wget nlohmann-json3-dev libgmp-dev nasm g++ build-essential
- name: Setup Circom
run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom
run: sudo wget https://github.com/iden3/circom/releases/download/v2.1.9/circom-linux-amd64 -O /usr/bin/circom && sudo chmod +x /usr/bin/circom
- name: Install dependencies
run: yarn

View File

@@ -111,7 +111,7 @@ jobs:
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update && sudo apt-get install -y wget nlohmann-json3-dev libgmp-dev nasm g++ build-essential
wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom
sudo wget https://github.com/iden3/circom/releases/download/v2.1.9/circom-linux-amd64 -O /usr/bin/circom && sudo chmod +x /usr/bin/circom
yarn workspace @semaphore-protocol/group build
yarn workspace @semaphore-protocol/identity build
yarn workspace @semaphore-protocol/proof build

View File

@@ -17,6 +17,16 @@ jobs:
with:
fetch-depth: 0
- name: Install soldeer
uses: taiki-e/install-action@v2
with:
tool: soldeer
- name: Store soldeer login credential
env:
SOLDEER_TOKEN: ${{ secrets.SOLDEER_TOKEN }}
run: |
echo "$SOLDEER_TOKEN" > "$GITHUB_WORKSPACE"/soldeer_login
- name: Install Node.js
uses: actions/setup-node@v4
with:
@@ -37,6 +47,7 @@ jobs:
run: yarn version:publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
SOLDEER_LOGIN_FILE: ${{ github.workspace }}/soldeer_login
- run: yarn version:release
env:

2
.gitignore vendored
View File

@@ -94,3 +94,5 @@ typechain-types
# Other
snark-artifacts
*.zip

1
.soldeerignore Normal file
View File

@@ -0,0 +1 @@
package.json

View File

@@ -53,7 +53,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.pse.dev/discord">
<a href="https://semaphore.pse.dev/telegram">
🗣️ Chat &amp; Support
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
@@ -264,7 +264,7 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
</a>
</td>
</tr>
<tbody>
</tbody>
</table>
## 🛠 Install
@@ -374,5 +374,5 @@ yarn version:bump <version>
# e.g. yarn version:bump 2.0.0
```
It will create a commit and a git tag that will need to pushed on the main branch. A workflow will be triggered and will
It will create a commit and a git tag that will need to be pushed on the main branch. A workflow will be triggered and will
publish the Semaphore packages on [npm](https://www.npmjs.com/) and release a new version on Github with its changelogs automatically.

View File

@@ -34,7 +34,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.pse.dev/discord">
<a href="https://semaphore.pse.dev/telegram">
🗣️ Chat &amp; Support
</a>
</h4>

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -67,7 +67,7 @@ const config: Config = {
announcementBar: {
id: "semaphore-v4-beta",
content:
'<b><a href="https://github.com/semaphore-protocol/semaphore/releases/tag/v4.0.0" target="_blank">Semaphore V4</a> is out 🎉 <a href="/getting-started">Try it out</a> and let us know if you have any feedback on <a href="https://semaphore.pse.dev/discord" target="_blank">Discord</a> or <a href="https://github.com/orgs/semaphore-protocol/discussions" target="_blank">Github</a>!</b>',
'<b><a href="https://github.com/semaphore-protocol/semaphore/releases/tag/v4.0.0" target="_blank">Semaphore V4</a> is out 🎉 <a href="/getting-started">Try it out</a> and let us know if you have any feedback on <a href="https://semaphore.pse.dev/telegram" target="_blank">Telegram</a> or <a href="https://github.com/orgs/semaphore-protocol/discussions" target="_blank">Github</a>!</b>',
backgroundColor: "#dde6fc",
textColor: "#000000"
},

View File

@@ -386,7 +386,7 @@
"message": "Connect"
},
"footer.right.connect.link1": {
"message": "Discord"
"message": "Telegram"
},
"footer.right.connect.link2": {
"message": "X (Twitter)"

View File

@@ -386,7 +386,7 @@
"message": "Conecta con nosotros"
},
"footer.right.connect.link1": {
"message": "Discord"
"message": "Telegram"
},
"footer.right.connect.link2": {
"message": "X (Twitter)"

View File

@@ -14,7 +14,7 @@ También proporciona un mecanismo simple para evitar la doble señalización, lo
## ¿Dónde puedo hacer preguntas sobre Semaphore?
Puede hacer preguntas sobre Semaphore en [Discord](https://semaphore.pse.dev/discord) o abriendo un [Semaphore Discussion](https://github.com/semaphore-protocol/semaphore/discussions).
Puede hacer preguntas sobre Semaphore en [Telegram](https://semaphore.pse.dev/telegram) o abriendo un [Semaphore Discussion](https://github.com/semaphore-protocol/semaphore/discussions).
## ¿Por qué las identidades requieren tanto `identity trapdoor` como `identity nullifier`?

View File

@@ -7,7 +7,7 @@ import TabItem from "@theme/TabItem"
# Solución de problemas
Si estas sugerencias no funcionan, no dude en preguntar en las [Semaphore Discussions](https://github.com/semaphore-protocol/semaphore/discussions) o en el canal `dev-chat` en el [Semaphore Discord](https://semaphore.pse.dev/discord).
Si estas sugerencias no funcionan, no dude en preguntar en las [Semaphore Discussions](https://github.com/semaphore-protocol/semaphore/discussions) o en el canal [Semaphore Telegram](https://semaphore.pse.dev/telegram).
## Usando Semaphore en the frontend

View File

@@ -16,10 +16,10 @@
"format:write": "remark ./**/*.mdx --output"
},
"dependencies": {
"@docusaurus/core": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/core": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@mdx-js/react": "^3.0.0",
"@semaphore-protocol/utils": "4.0.2",
"@semaphore-protocol/utils": "4.6.0",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.2.1",
"docusaurus-plugin-sass": "^0.2.5",
@@ -31,8 +31,8 @@
"url-loader": "^4.1.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/tsconfig": "3.1.1",
"@docusaurus/module-type-aliases": "3.5.2",
"@docusaurus/tsconfig": "3.5.2",
"@types/react": "^18.2.29",
"remark-cli": "^12.0.0",
"remark-frontmatter": "^5.0.0",

View File

@@ -32,7 +32,7 @@ function Footer() {
id: "footer.right.connect.title"
})}
</h3>
<Link href="https://semaphore.pse.dev/discord" target="_blank">
<Link href="https://semaphore.pse.dev/telegram" target="_blank">
{translate({
id: "footer.right.connect.link1"
})}

View File

@@ -21,7 +21,7 @@ The first external nullifier will be added as an external nullifier to the
contract, and this external nullifier will be active once the deployment
completes.
## Add, deactivate, or reactivate external nullifiiers
## Add, deactivate, or reactivate external nullifiers
**Contract ABI**:

View File

@@ -10,8 +10,8 @@ An identity is comprised of the following information:
1. An [EdDSA](https://en.wikipedia.org/wiki/EdDSA) private key. Note that it is
_not_ an Ethereum private key.
2. An identity nullifier, whih is a random 32-byte value.
3. An identity trapdoor, whih is a random 32-byte value.
2. An identity nullifier, which is a random 32-byte value.
3. An identity trapdoor, which is a random 32-byte value.
An identity commitment is the Pedersen hash of:

View File

@@ -204,7 +204,7 @@ It returns an object as such:
- `msg`: The hash of the external nullifier and the signal hash
- `signature`: The signature on the above msg.
- `tree`: The Merkle tree object after it has been updated with the identity commitment
- `identityPath`: The Merkle path to the identity commmitment
- `identityPath`: The Merkle path to the identity commitment
- `identityPathIndex`: The leaf index of the identity commitment
- `identityPathElements`: The elements along the above Merkle path

View File

@@ -26,7 +26,7 @@ With the Client contract as the owner of the Semaphore contract, the Client
contract may call owner-only Semaphore functions such as
`addExternalNullifier()`.
## Add, deactivate, or reactivate external nullifiiers
## Add, deactivate, or reactivate external nullifiers
These functions add, deactivate, and reactivate an external nullifier respectively.
As each identity can only signal once to an external nullifier, and as a signal

View File

@@ -14,7 +14,7 @@ It also provides a simple mechanism to prevent double-signaling, which means you
## Where can I ask questions about Semaphore?
You can ask questions about Semaphore on [Discord](https://semaphore.pse.dev/discord) or by opening a [Semaphore Discussion](https://github.com/semaphore-protocol/semaphore/discussions).
You can ask questions about Semaphore on [Telegram](https://semaphore.pse.dev/telegram) or by opening a [Semaphore Discussion](https://github.com/semaphore-protocol/semaphore/discussions).
## Why do identities require both the `identity trapdoor` and the `identity nullifier`?

View File

@@ -37,7 +37,7 @@ For more information, see [Merkle tree in Wikipedia](https://en.wikipedia.org/wi
## Nullifier
A value used to prevent double entry or double signalling.
A value used to prevent double entry or double signaling.
See [Circuit nullifier hash](/V3/technical-reference/circuits/#nullifier-hash).

View File

@@ -7,7 +7,7 @@ import TabItem from "@theme/TabItem"
# Troubleshooting
If these suggestions do not work, feel free to ask in the [Semaphore Discussions](https://github.com/semaphore-protocol/semaphore/discussions) or in the `dev-chat` channel in the [Semaphore Discord](https://semaphore.pse.dev/discord).
If these suggestions do not work, feel free to ask in the [Semaphore Discussions](https://github.com/semaphore-protocol/semaphore/discussions) or in the [Semaphore Telegram](https://semaphore.pse.dev/telegram).
## Using Semaphore in the frontend

View File

@@ -0,0 +1,131 @@
---
sidebar_position: 8
---
# Benchmarks
Semaphore v4 introduces new features and performance improvements. Detailed changes are available in the [release changelog](https://github.com/semaphore-protocol/semaphore/releases/tag/v4.0.0).
The primary changes in benchmark values are driven by the two major protocol updates:
- New [Identity schema](https://github.com/semaphore-protocol/semaphore/tree/main/packages/identity).
- [LeanIMT](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/lean-imt): New optimized IMT.
## New Identity schema
Semaphore v4 includes a new schema for the identities allowing the creation and verification of signatures.
Although the new Identity package is slightly slower than the one in Semaphore v3 (by just a few milliseconds), the trade-off is minimal considering the included functionality.
The new identity schema adds additional constraints to the Semaphore v4 circuit; however, this does not negatively impact overall performance of the protocol.
## LeanIMT
Semaphore v4 uses the LeanIMT data structure for group operations, an improvement over the IMT used in v3.
For further details, see the [LeanIMT paper](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/papers/leanimt).
The LeanIMT offers dynamic depth, resulting in faster and more cost-effective group operations, across Node.js, browser and smart contracts.
Semaphore v4 support tree depths from 1 to 32, while v3 supports depths from 16 to 32, both for proof generation and verification.
## System Specifications and Software environment
All the benchmarks were run in an environment with these properties:
**System Specifications**
Computer: MacBook Pro
Chip: Apple M2 Pro
Memory (RAM): 16 GB
Operating System: macOS Sequoia version 15.0.1
**Software environment**
Node.js version: 20.18.0
Browser: Google Chrome Version 130.0.6723.92 (Official Build) (arm64)
## Running the benchmarks
### Javascript and Circom
GitHub repository to the run Node.js, browser and Circom benchmarks: https://github.com/vplasencia/semaphore-benchmarks
### Solidity
GitHub repository to run the Solidity benchmarks: https://github.com/semaphore-protocol/semaphore
## Node.js benchmarks
### Identities
![Identities](../../assets/img-benchmarks/node/identity-node-benchmarks.png)
### Create Group
Create group, either empty or with initial members.
![Create Group](../../assets/img-benchmarks/node/create-group-node-benchmarks.png)
### Add Member
Add a member to groups with different sizes.
![Add Member](../../assets/img-benchmarks/node/add-member-node-benchmarks.png)
### Generate Proof
Generate a proof using groups with different numbers of members.
![Generate Proof](../../assets/img-benchmarks/node/generate-proof-node-benchmarks.png)
### Verify Proof
Verify a proof using groups with different numbers of members.
![Verify Proof](../../assets/img-benchmarks/node/verify-proof-node-benchmarks.png)
## Browser benchmarks
Main functions used in the browser.
![Browser benchmarks](../../assets/img-benchmarks/browser/browser-benchmarks.png)
## Circuit benchmarks
### Semaphore V3
<img
src={require('../../assets/img-benchmarks/circuits/semaphore-v3-circuits-benchmarks.png').default}
alt="Circuit benchmarks Semaphore v3"
style={{ width: '300px', height: 'auto' }}
/>
### Semaphore V4
<img
src={require('../../assets/img-benchmarks/circuits/semaphore-v4-circuits-benchmarks.png').default}
alt="Circuit benchmarks Semaphore v4"
style={{ width: '300px', height: 'auto' }}
/>
## Contracts
<img
src={require('../../assets/img-benchmarks/contracts/contract-benchmarks.png').default}
alt="Contract Benchmarks"
style={{ width: '590px', height: 'auto' }}
/>
### Semaphore V3 gas report
![Contracts benchmarks Semaphore v3](../../assets/img-benchmarks/contracts/semaphore-v3-contracts-benchmarks.png)
### Semaphore V4 gas report
![Contracts benchmarks Semaphore v4](../../assets/img-benchmarks/contracts/semaphore-v4-contracts-benchmarks.png)

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 12
sidebar_position: 13
---
# Credits

View File

@@ -1,12 +1,12 @@
---
sidebar_position: 11
sidebar_position: 12
---
# FAQ
## Where can I ask questions about Semaphore?
You can ask questions about Semaphore on [Discord](https://semaphore.pse.dev/discord) or by opening a [Semaphore Discussion](https://github.com/semaphore-protocol/semaphore/discussions). The most frequent questions will be listed below.
You can ask questions about Semaphore on [Telegram](https://semaphore.pse.dev/telegram) or by opening a [Semaphore Discussion](https://github.com/semaphore-protocol/semaphore/discussions). The most frequent questions will be listed below.
## Why should I prevent proofs from being verified twice?

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 8
sidebar_position: 9
---
# Glossary

View File

@@ -109,3 +109,33 @@ After a message is signed, anyone can verify the signature using the message its
// Static method.
Identity.verifySignature(message, signature, identity1.publicKey)
```
## Export and import an identity
A Semaphore Identity can be exported and then imported later for reuse.
### Export an identity
Returns the private key encoded as a base64 string.
```ts
import { Identity } from "@semaphore-protocol/identity"
const identity = new Identity()
const privateKey = identity.export()
```
### Import an identity
Returns a Semaphore identity based on a private key encoded as a base64 string.
```ts
import { Identity } from "@semaphore-protocol/identity"
const identity = new Identity()
const privateKey = identity.export()
const identity2 = Identity.import(privateKey)
```

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 9
sidebar_position: 10
---
import Articles from '@site/src/components/Articles';

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 10
sidebar_position: 11
---
import Tabs from "@theme/Tabs"
@@ -7,7 +7,7 @@ import TabItem from "@theme/TabItem"
# Troubleshooting
If these suggestions do not work, feel free to ask for more help and support on [Github Discussions](https://github.com/semaphore-protocol/semaphore/discussions) or [Discord](https://semaphore.pse.dev/discord) ("dev-chat" channel).
If these suggestions do not work, feel free to ask for more help and support on [Github Discussions](https://github.com/semaphore-protocol/semaphore/discussions) or [Telegram](https://semaphore.pse.dev/telegram).
## Transaction reverted when using the same nullifier

View File

@@ -31,7 +31,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.pse.dev/discord">
<a href="https://semaphore.pse.dev/telegram">
🗣️ Chat &amp; Support
</a>
</h4>
@@ -42,15 +42,21 @@
## Networks
| Semaphore version | Sepolia | Polygon Mumbai | Optimism Sepolia | Arbitrum Sepolia | Arbitrum One | Polygon Amoy |
| ----------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| v2.0 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) | N/A |
| v2.5 | N/A | N/A | N/A | N/A | N/A | N/A |
| v2.6 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) | N/A |
| v3.0 - v3.1 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) | N/A |
| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | N/A | N/A | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) | N/A |
| v4.0.0-beta | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v4.0.0-beta) | [semaphore-matic-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-matic-mumbai/v4.0.0-beta) | [semaphore-optimism-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-optimism-sepolia/v4.0.0-beta) | [semaphore-arbitrum-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum-sepolia/v4.0.0-beta) | N/A | N/A |
| >= v4.0.0-beta.17 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v4.0.0-beta.17) | N/A | [semaphore-optimism-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-optimism-sepolia/v4.0.0-beta.17) | [semaphore-arbitrum-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum-sepolia/v4.0.0-beta.17) | N/A | [semaphore-matic-amoy](https://api.studio.thegraph.com/query/14377/semaphore-matic-amoy/v4.0.0-beta.17) |
You can access any subgraph supported by Semaphore with the following URL: https://api.studio.thegraph.com/query/14377/semaphore-<network-name>/v4.1.0.
Supported networks:
- `sepolia`
- `optimism`
- `optimism-sepolia`
- `arbitrum`
- `arbitrum-sepolia`
- `matic`
- `matic-amoy`
- `base-sepolia`
- `base`
- `linea-sepolia`
- `linea`
## 🛠 Install

View File

@@ -20,7 +20,7 @@
"dependencies": {
"@graphprotocol/graph-cli": "0.78.0",
"@graphprotocol/graph-ts": "0.35.1",
"@semaphore-protocol/utils": "4.0.2"
"@semaphore-protocol/utils": "workspace:packages/utils"
},
"devDependencies": {
"@types/mustache": "^4.2.2",

View File

@@ -6,8 +6,20 @@ const network = process.argv.at(2)
const template = readFileSync("./subgraph.template.yaml", "utf-8")
function mapNetwork(n) {
if (n === "matic-amoy") {
return "polygon-amoy"
}
if (n === "arbitrum") {
return "arbitrum-one"
}
return network
}
const subgraph = Mustache.render(template, {
network: network === "matic-amoy" ? "polygon-amoy" : network,
network: mapNetwork(network),
...getDeployedContract(network)
})

View File

@@ -34,7 +34,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.pse.dev/discord">
<a href="https://semaphore.pse.dev/telegram">
🗣️ Chat &amp; Support
</a>
</h4>

View File

@@ -5,7 +5,7 @@ import Carousel from "../components/Carousel"
import ProjectCard from "../components/ProjectCard"
import events from "../data/events.json"
import allProjects from "../data/projects.json"
import IconDiscord from "../icons/IconDiscord"
import IconTelegram from "../icons/IconTelegram"
import HRoadmap from "@/components/HRoadmap"
import VRoadmap from "@/components/VRoadmap"
@@ -243,9 +243,9 @@ export default function Home() {
Ask questions, suggest ideas, stay up-to-date, and meet other people building privacy
applications with Zero Knowledge.
</Text>
<Link href="https://semaphore.pse.dev/discord" isExternal>
<Button leftIcon={<IconDiscord />} size="lg">
Discord
<Link href="https://semaphore.pse.dev/telegram" isExternal>
<Button leftIcon={<IconTelegram />} size="lg">
Telegram
</Button>
</Link>
</VStack>

View File

@@ -2,7 +2,7 @@ import { Divider, Heading, HStack, Link, Stack, Text, VStack } from "@chakra-ui/
import Image from "next/image"
import NextLink from "next/link"
import IconArrowUpRight from "../icons/IconArrowUpRight"
import IconDiscord from "../icons/IconDiscord"
import IconTelegram from "../icons/IconTelegram"
import IconThumbsUp from "../icons/IconThumbsUp"
import IconX from "@/icons/IconX"
@@ -59,11 +59,11 @@ export default function Footer() {
<Divider />
<VStack spacing="5">
<HStack spacing="5">
<Link href="https://semaphore.pse.dev/discord" isExternal>
<Link href="https://semaphore.pse.dev/telegram" isExternal>
<HStack>
<IconDiscord boxSize={{ base: "16px", md: "24px" }} />
<IconTelegram boxSize={{ base: "16px", md: "24px" }} />
<Heading fontSize={{ base: "14px", md: "18px" }} fontWeight="normal">
Discord
Telegram
</Heading>
</HStack>
</Link>

View File

@@ -20,7 +20,7 @@ import {
} from "@chakra-ui/react"
import Image from "next/image"
import NextLink from "next/link"
import IconDiscord from "../icons/IconDiscord"
import IconTelegram from "../icons/IconTelegram"
import IconMenu from "../icons/IconMenu"
import IconThumbsUp from "../icons/IconThumbsUp"
import NavbarLinks from "./NavbarLinks"
@@ -71,11 +71,11 @@ export default function Navbar() {
<VStack w="full" justify="center" spacing="5">
<HStack spacing="5">
<Link href="https://semaphore.pse.dev/discord" isExternal>
<Link href="https://semaphore.pse.dev/telegram" isExternal>
<HStack>
<IconDiscord boxSize={{ base: "16px", md: "24px" }} />
<IconTelegram boxSize={{ base: "16px", md: "24px" }} />
<Heading fontSize={{ base: "14px", md: "18px" }} fontWeight="normal">
Discord
Telegram
</Heading>
</HStack>
</Link>

View File

@@ -1,9 +1,9 @@
[
{
"name": "ETHRome",
"date": "Oct 4-6, 2024",
"description": "Semaphore team will deliver an in-person talk and sponsor prizes for the hackathon.",
"link": "https://www.ethrome.org/"
"name": "DIF - Decentralized Identity Foundation",
"date": "Oct 1 - Nov 4, 2024",
"description": "Semaphore team will deliver a talk and sponsor prizes for the online hackathon.",
"link": "https://identity.foundation/"
},
{
"name": "Devcon",

View File

@@ -32,6 +32,16 @@
"website": "https://bandada.pse.dev"
}
},
{
"name": "ZkDemocracy",
"categories": ["Infra", "Data"],
"tagline": "ZkDemocracy is a ready-to-use anonymous voting system based on Semaphore.",
"pse": false,
"icon": "",
"links": {
"github": "https://github.com/TheBojda/zkdemocracy"
}
},
{
"name": "Super Secret ZK Ballot",
"tagline": "A Solidity contract for anonymous voting.",
@@ -115,20 +125,7 @@
"icon": "",
"links": {
"website": "https://explorer.semaphore.pse.dev",
"github": "https://github.com/semaphore-protocol/explorer",
"discord": "https://semaphore.pse.dev/discord"
}
},
{
"name": "Semaphore Discord Bot",
"tagline": "A Discord Bot for Semaphore",
"categories": ["Development"],
"pse": true,
"icon": "",
"links": {
"website": "https://discord.com/api/oauth2/authorize?client_id=1082429985496772628&permissions=1024&scope=bot",
"github": "https://github.com/semaphore-protocol/discord-bot",
"discord": "https://semaphore.pse.dev/discord"
"github": "https://github.com/semaphore-protocol/explorer"
}
},
{
@@ -558,5 +555,15 @@
"github": "https://github.com/GeneralMagicio/pairwise-RPGF4",
"website": "https://pairwise.vote"
}
},
{
"name": "VeilEscrow",
"categories": ["Privacy", "Coordination"],
"tagline": "Secure, private, and decentralized escrows for anonymous transactions",
"pse": false,
"icon": "",
"links": {
"github": "https://github.com/evgongora/VeilEscrow"
}
}
]

View File

@@ -17,18 +17,22 @@
},
{
"name": "LeanIMT Paper",
"done": true
},
{
"name": "Support more Testnets/Mainnets",
"done": true
},
{
"name": "Documentation improvements",
"done": false
},
{
"name": "New Explorer",
"done": false
},
{
"name": "RLN extension",
"done": false
},
{
"name": "Support more Testnets/Mainnets",
"done": false
},
{
"name": "Documentation Revamp",
"done": false
}
]

View File

@@ -0,0 +1,13 @@
import { Icon, IconProps } from "@chakra-ui/react"
import React from "react"
export default function IconTelegram(props: IconProps): JSX.Element {
return (
<Icon viewBox="0 0 48 48" {...props}>
<path
d="M41.4193 7.30899C41.4193 7.30899 45.3046 5.79399 44.9808 9.47328C44.8729 10.9883 43.9016 16.2908 43.1461 22.0262L40.5559 39.0159C40.5559 39.0159 40.3401 41.5048 38.3974 41.9377C36.4547 42.3705 33.5408 40.4227 33.0011 39.9898C32.5694 39.6652 24.9068 34.7955 22.2086 32.4148C21.4531 31.7655 20.5897 30.4669 22.3165 28.9519L33.6487 18.1305C34.9438 16.8319 36.2389 13.8019 30.8426 17.4812L15.7331 27.7616C15.7331 27.7616 14.0063 28.8437 10.7686 27.8698L3.75342 25.7055C3.75342 25.7055 1.16321 24.0823 5.58815 22.459C16.3807 17.3729 29.6555 12.1786 41.4193 7.30899Z"
fill="currentColor"
/>
</Icon>
)
}

View File

@@ -23,12 +23,12 @@
"format": "concurrently -c auto -g -n prettier,embark \"prettier -c .\" \"yarn workspace semaphore-docs format\"",
"format:write": "concurrently -c auto -g -n prettier,embark \"prettier -w .\" \"yarn workspace semaphore-docs format:write\"",
"docs": "typedoc",
"version:bump": "yarn workspaces foreach -A --no-private version -d ${0} && yarn version apply --all && yarn remove:stable-version-field && NO_HOOK=1 git commit -am \"chore: v${0}\" && git tag v${0}",
"version:publish": "yarn build:libraries && yarn clean:cli-templates && yarn workspaces foreach -A --no-private npm publish --tolerate-republish --access public",
"version:bump": "scripts/version.ts ${0}",
"version:publish": "scripts/publish.ts",
"version:release": "changelogithub",
"clean": "ts-node scripts/clean-apps.ts && ts-node scripts/clean-packages.ts && yarn clean:cli-templates && rimraf node_modules",
"clean:cli-templates": "ts-node scripts/clean-cli-templates.ts",
"remove:stable-version-field": "ts-node scripts/remove-stable-version-field.ts && yarn format:write",
"clean": "scripts/clean-apps.ts && scripts/clean-packages.ts && yarn clean:cli-templates && rimraf node_modules",
"clean:cli-templates": "scripts/clean-cli-templates.ts",
"remove:stable-version-field": "scripts/remove-stable-version-field.ts && yarn format:write",
"precommit": "lint-staged",
"postinstall": "husky && git config --local core.editor cat"
},
@@ -57,6 +57,7 @@
"@types/glob": "^7.2.0",
"@types/jest": "^29.5.12",
"@types/node": "^20",
"@types/semver": "^7",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"changelogithub": "0.12.7",
@@ -78,6 +79,7 @@
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"semver": "^7.6.2",
"snarkjs": "0.7.4",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",

View File

@@ -34,7 +34,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.pse.dev/discord">
<a href="https://semaphore.pse.dev/telegram">
🗣️ Chat &amp; Support
</a>
</h4>

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/circuits",
"version": "4.0.2",
"version": "4.6.0",
"description": "Semaphore Circom circuits to generate zero-knowledge proofs.",
"license": "MIT",
"files": [
@@ -30,9 +30,9 @@
"devDependencies": {
"@semaphore-protocol/core": "workspace:^",
"@types/mocha": "^10.0.6",
"@zk-kit/baby-jubjub": "1.0.1",
"circomkit": "^0.0.19",
"@zk-kit/baby-jubjub": "1.0.3",
"circomkit": "0.0.19",
"mocha": "^10.2.0",
"poseidon-lite": "^0.2.0"
"poseidon-lite": "^0.3.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/cli-template-contracts-hardhat",
"version": "4.0.2",
"version": "4.6.0",
"description": "Semaphore Hardhat template.",
"license": "Unlicense",
"files": [
@@ -41,9 +41,9 @@
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@semaphore-protocol/core": "4.0.2",
"@semaphore-protocol/hardhat": "4.0.2",
"@semaphore-protocol/utils": "4.0.2",
"@semaphore-protocol/core": "4.6.0",
"@semaphore-protocol/hardhat": "4.6.0",
"@semaphore-protocol/utils": "4.6.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
@@ -71,7 +71,7 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@semaphore-protocol/contracts": "4.0.2"
"@semaphore-protocol/contracts": "4.6.0"
},
"packageManager": "yarn@4.1.0"
}

View File

@@ -20,9 +20,9 @@
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@semaphore-protocol/core": "4.0.2",
"@semaphore-protocol/hardhat": "4.0.2",
"@semaphore-protocol/utils": "4.0.2",
"@semaphore-protocol/core": "4.6.0",
"@semaphore-protocol/hardhat": "4.6.0",
"@semaphore-protocol/utils": "4.6.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
@@ -50,7 +50,7 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@semaphore-protocol/contracts": "4.0.2"
"@semaphore-protocol/contracts": "4.6.0"
},
"packageManager": "yarn@4.1.0"
}

View File

@@ -1,4 +1,4 @@
NEXT_PUBLIC_DEFAULT_NETWORK=localhost
NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9
NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
NEXT_PUBLIC_GROUP_ID=0

View File

@@ -1,5 +1,8 @@
NEXT_PUBLIC_DEFAULT_NETWORK=sepolia
NEXT_PUBLIC_INFURA_API_KEY=abf67af1010b4b8d877e04244f1eac3d
NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=
NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x1e0d7FF1610e480fC93BdEC510811ea2Ba6d7c2f
NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK=https://api.defender.openzeppelin.com/actions/20fce2ae-844b-4ec0-a6a2-90a3350a9d2c/runs/webhook/303216d1-fa7d-4fca-8c5b-7ba1ba544fc7/2T7i9xrkZA5j37hoaQLUuw
NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT=https://api.gelato.digital/relays/v2/sponsored-call
NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID=11155111
NEXT_PUBLIC_GROUP_ID=

View File

@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# testing
/coverage
# next.js
/.next/
/out/
@@ -7,3 +10,18 @@
# production
/build
# misc
.DS_Store
*.pem
# typescript
*.tsbuildinfo
next-env.d.ts
# 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

@@ -9,11 +9,6 @@
"internalType": "address",
"name": "semaphoreAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "_groupId",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
@@ -92,8 +87,8 @@
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b5060405161083f38038061083f833981810160405281019061003291906101a8565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c96e71fb600154306040518363ffffffff1660e01b81526004016100d6929190610206565b600060405180830381600087803b1580156100f057600080fd5b505af1158015610104573d6000803e3d6000fd5b50505050505061022f565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061013f82610114565b9050919050565b61014f81610134565b811461015a57600080fd5b50565b60008151905061016c81610146565b92915050565b6000819050919050565b61018581610172565b811461019057600080fd5b50565b6000815190506101a28161017c565b92915050565b600080604083850312156101bf576101be61010f565b5b60006101cd8582860161015d565b92505060206101de85828601610193565b9150509250929050565b6101f181610172565b82525050565b61020081610134565b82525050565b600060408201905061021b60008301856101e8565b61022860208301846101f7565b9392505050565b6106018061023e6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033",
"bytecode": "0x608060405234801561001057600080fd5b506040516108473803806108478339818101604052810190610032919061017d565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635c3f3b60306040518263ffffffff1660e01b81526004016100cb91906101b9565b6020604051808303816000875af11580156100ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061010e919061020a565b60018190555050610237565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061014a8261011f565b9050919050565b61015a8161013f565b811461016557600080fd5b50565b60008151905061017781610151565b92915050565b6000602082840312156101935761019261011a565b5b60006101a184828501610168565b91505092915050565b6101b38161013f565b82525050565b60006020820190506101ce60008301846101aa565b92915050565b6000819050919050565b6101e7816101d4565b81146101f257600080fd5b50565b600081519050610204816101de565b92915050565b6000602082840312156102205761021f61011a565b5b600061022e848285016101f5565b91505092915050565b610601806102466000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea264697066735822122078569abd7f309f3107c4d19e9b4a4f4812522ccc5dc57c7ccbe2b06a5ba461b064736f6c63430008170033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea264697066735822122078569abd7f309f3107c4d19e9b4a4f4812522ccc5dc57c7ccbe2b06a5ba461b064736f6c63430008170033",
"linkReferences": {},
"deployedLinkReferences": {}
}

View File

@@ -14,7 +14,8 @@ const nextConfig = withPWA({
})({
env: {
INFURA_API_KEY: process.env.INFURA_API_KEY,
ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY
ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY,
GELATO_RELAYER_API_KEY: process.env.GELATO_RELAYER_API_KEY
}
})

View File

@@ -9,9 +9,9 @@
"lint": "next lint"
},
"dependencies": {
"@semaphore-protocol/core": "4.0.2",
"@semaphore-protocol/data": "4.0.2",
"@semaphore-protocol/utils": "4.0.2",
"@semaphore-protocol/core": "4.6.0",
"@semaphore-protocol/data": "4.6.0",
"@semaphore-protocol/utils": "4.6.0",
"ethers": "^6.11.1",
"next": "14.1.0",
"next-pwa": "^5.6.0",

View File

@@ -3,26 +3,14 @@ import { NextRequest } from "next/server"
import Feedback from "../../../../contract-artifacts/Feedback.json"
export async function POST(req: NextRequest) {
if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") {
throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file")
}
if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") {
throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file")
}
if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") {
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.NEXT_PUBLIC_DEFAULT_NETWORK
const infuraApiKey = process.env.INFURA_API_KEY
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS
const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK as string
const infuraApiKey = process.env.NEXT_PUBLIC_INFURA_API_KEY as string
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string
const provider =
ethereumNetwork === "localhost"

View File

@@ -3,26 +3,14 @@ import { NextRequest } from "next/server"
import Feedback from "../../../../contract-artifacts/Feedback.json"
export async function POST(req: NextRequest) {
if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") {
throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file")
}
if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") {
throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file")
}
if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") {
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.NEXT_PUBLIC_DEFAULT_NETWORK
const infuraApiKey = process.env.INFURA_API_KEY
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS
const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK as string
const infuraApiKey = process.env.NEXT_PUBLIC_INFURA_API_KEY as string
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string
const provider =
ethereumNetwork === "localhost"

View File

@@ -1,29 +1,40 @@
:root {
--slate100: #f1f5f9;
--slate200: #e2e8f0;
--slate300: #cbd5e1;
--slate400: #94a3b8;
--slate500: #64748b;
--slate700: #334155;
--blue200: #bfdbfe;
--blue500: #3b82f6;
--blue600: #2563eb;
--blue700: #1d4ed8;
--blue800: #1e40af;
--blue900: #1e3a8a;
--blue100: #dde6fc;
--blue200: #c3d4fa;
--blue300: #9abaf6;
--blue400: #6a95f0;
--blue500: #4771ea;
--blue600: #3555df;
--blue700: #2940cc;
--blue800: #2735a6;
--blue900: #253183;
--blue950: #1b2050;
--darkBlueBg: #00020d;
--slate50: #f7f7f8;
--slate100: #eeeef0;
--slate200: #d9d9de;
--slate300: #b8b9c1;
--slate400: #92939e;
--slate500: #747583;
--slate600: #5e5f6b;
--slate700: #4d4e57;
--slate800: #42424a;
--slate900: #3a3a40;
--slate950: #26262b;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Outfit", sans-serif;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
height: 100vh;
}
body {
@@ -33,17 +44,30 @@ body {
p {
line-height: 1.5rem;
font-weight: 300;
overflow-wrap: break-word;
font-size: 1rem;
}
b {
font-weight: 600;
}
.key-wrapper {
padding-bottom: 1.5rem;
padding-left: 0.5rem;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
max-width: 32rem;
max-width: 40vw;
min-width: 35rem;
margin: auto;
padding: 1rem;
height: calc(100vh - 7rem - 1px);
padding-bottom: 5rem;
min-height: calc(100vh - 3.5rem);
}
@@ -57,28 +81,25 @@ p {
margin-bottom: 1rem;
}
ol {
padding: 1rem;
}
li {
margin-top: 1rem;
}
h2 {
font-size: 2.25rem;
font-weight: 500;
margin-bottom: 1rem;
line-height: 1.2;
}
h3 {
font-size: 1.125rem;
font-size: 1.15rem;
font-weight: 500;
}
.divider {
height: 1px;
background: var(--slate500);
opacity: 0.6;
border: 0;
border-style: solid;
border-bottom-width: 1px;
width: 100%;
border-color: var(--slate400);
margin: 2rem 0;
}
@@ -89,31 +110,44 @@ h3 {
}
a {
color: var(--blue500);
color: var(--blue400);
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration-color: var(--blue400);
}
.button {
background-color: var(--blue800);
width: 100%;
padding: 0.8rem 1rem;
border: 1px;
border-radius: 100px;
cursor: pointer;
color: var(--slate100);
font-size: 1.125rem;
font-weight: 500;
transition: all 200ms linear;
margin-top: 1rem;
margin-bottom: 1.5rem;
opacity: 0.9;
font-weight: 400;
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-duration: 200ms;
padding-left: 18px !important;
padding-right: 18px !important;
height: 2.5rem;
min-width: 2.5rem;
font-size: 1rem;
-webkit-padding-start: 1rem;
padding-inline-start: 1rem;
-webkit-padding-end: 1rem;
padding-inline-end: 1rem;
background: var(--blue500);
color: white;
background-image: linear-gradient(to right, var(--blue500), var(--blue800));
transition-timing-function: linear;
width: 100%;
border: none;
display: flex;
justify-content: center;
height: 3rem;
align-items: center;
justify-content: center;
}
.button:hover {
background-color: var(--blue900);
background-color: var(--blue800);
background-image: none;
cursor: pointer;
}
.button:disabled {
@@ -123,54 +157,126 @@ a {
.button:disabled:hover {
background-color: var(--blue800);
background-image: none;
}
.button-stepper {
cursor: pointer;
color: var(--blue600);
color: var(--blue500);
font-size: 1.1rem;
border: none;
background: none;
width: 4rem;
margin: 0 1rem;
display: flex;
justify-content: center;
}
.button-link {
.button-stepper:hover {
text-decoration: underline;
}
.refresh-wrapper {
color: var(--slate400);
display: flex;
align-items: center;
}
.refresh-wrapper:hover {
text-decoration: underline;
}
.refresh-button {
cursor: pointer;
color: var(--slate300);
font-size: 1.1rem;
border: none;
background: none;
padding-right: 1rem;
display: flex;
align-items: center;
}
.refresh-button:hover {
text-decoration: underline;
}
.refresh-span {
width: 1.5em;
height: 1em;
display: inline-block;
line-height: 1em;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: currentColor;
vertical-align: middle;
}
.refresh-icon {
margin-inline-end: 0.5rem;
}
.stepper-icon {
display: inline-flex;
align-self: center;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.left-pad {
margin-inline-end: 0.5rem;
}
.right-pad {
margin-inline-start: 0.5rem;
}
.stepper-icon svg {
width: 1em;
height: 1em;
display: inline-block;
line-height: 1em;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: currentColor;
vertical-align: super;
}
.box {
padding: 0.8rem;
border-style: solid;
border-width: 1px;
border-color: var(--slate500);
border-radius: 4px;
border-bottom: 1px solid white;
padding: 0.8rem 0;
max-height: 50px;
}
.box-text {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0.5rem;
font-size: 1rem;
}
.header {
display: flex;
justify-content: space-between;
padding: 1.5rem;
padding: 0 1.5rem;
height: 3.5rem;
}
.header-left {
font-size: 1.125rem;
font-weight: 500;
text-decoration: none;
text-decoration: none !important;
display: flex;
align-items: center;
}
.header-right {
display: flex;
gap: 1.5rem;
align-items: center;
}
.footer {
@@ -188,6 +294,57 @@ a {
background: var(--slate500);
}
.users-wrapper,
.feedback-wrapper {
max-height: 300px;
overflow-y: scroll;
}
.keys-header {
margin-bottom: 1.5rem;
}
.users-header {
font-weight: 700;
font-size: 1.125rem;
font-family: "DM Sans", sans-serif;
}
.join-group-button,
.send-feedback-button {
margin-top: 1.5rem;
}
.github-icon {
width: 1.5rem;
height: 1.5rem;
display: inline-block;
line-height: 1em;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: currentColor;
}
.github-button {
cursor: pointer;
color: var(--slate100);
font-size: 1.1rem;
border: none;
background: none;
padding-right: 1rem;
display: flex;
align-items: center;
box-sizing: border-box;
display: inline-block;
overflow: visible !important;
fill: currentColor;
color: #f0f6fc !important;
vertical-align: middle !important;
width: 2rem;
height: 2rem;
}
.loader {
width: 25px;
height: 25px;
@@ -196,7 +353,7 @@ a {
border-right: 2px solid transparent;
animation: spin 1s linear infinite;
z-index: 20;
margin-left: 1rem;
margin-left: 0.5rem;
}
@keyframes spin {

View File

@@ -0,0 +1,177 @@
"use client"
import Stepper from "@/components/Stepper"
import { useLogContext } from "@/context/LogContext"
import { useSemaphoreContext } from "@/context/SemaphoreContext"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useMemo } from "react"
import Feedback from "../../../contract-artifacts/Feedback.json"
import { ethers } from "ethers"
import useSemaphoreIdentity from "@/hooks/useSemaphoreIdentity"
import { useState } from "react"
export default function GroupsPage() {
const router = useRouter()
const { setLog } = useLogContext()
const { _users, refreshUsers, addUser } = useSemaphoreContext()
const [_loading, setLoading] = useState(false)
const { _identity } = useSemaphoreIdentity()
useEffect(() => {
if (_users.length > 0) {
setLog(`${_users.length} user${_users.length > 1 ? "s" : ""} retrieved from the group 🤙🏽`)
}
}, [_users, setLog])
const users = useMemo(() => [..._users].reverse(), [_users])
const joinGroup = useCallback(async () => {
if (!_identity) {
return
}
setLoading(true)
setLog(`Joining the Feedback group...`)
let joinedGroup: boolean = false
if (process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK) {
const response = await fetch(process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
abi: Feedback.abi,
address: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string,
functionName: "joinGroup",
functionParameters: [_identity.commitment.toString()]
})
})
if (response.status === 200) {
joinedGroup = true
}
} else if (
process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT &&
process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID &&
process.env.GELATO_RELAYER_API_KEY
) {
const iface = new ethers.Interface(Feedback.abi)
const request = {
chainId: process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID,
target: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS,
data: iface.encodeFunctionData("joinGroup", [_identity.commitment.toString()]),
sponsorApiKey: process.env.GELATO_RELAYER_API_KEY
}
const response = await fetch(process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request)
})
if (response.status === 201) {
joinedGroup = true
}
} else {
const response = await fetch("api/join", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
identityCommitment: _identity.commitment.toString()
})
})
if (response.status === 200) {
joinedGroup = true
}
}
if (joinedGroup) {
addUser(_identity.commitment.toString())
setLog(`You have joined the Feedback group event 🎉 Share your feedback anonymously!`)
} else {
setLog("Some error occurred, please try again!")
}
setLoading(false)
}, [_identity, addUser, setLoading, setLog])
const userHasJoined = useMemo(
() => _identity !== undefined && _users.includes(_identity.commitment.toString()),
[_identity, _users]
)
return (
<>
<h2>Groups</h2>
<p>
<a
href="https://docs.semaphore.pse.dev/guides/groups"
target="_blank"
rel="noreferrer noopener nofollow"
>
Semaphore groups
</a>{" "}
are{" "}
<a
href="https://zkkit.pse.dev/classes/_zk_kit_imt.LeanIMT.html"
target="_blank"
rel="noreferrer noopener nofollow"
>
Lean incremental Merkle trees
</a>{" "}
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 className="users-header">Group users ({_users.length})</h3>
<button className="refresh-button" onClick={refreshUsers}>
<span className="refresh-span">
<svg viewBox="0 0 24 24" focusable="false" className="refresh-icon">
<path
fill="currentColor"
d="M5.463 4.43301C7.27756 2.86067 9.59899 1.99666 12 2.00001C17.523 2.00001 22 6.47701 22 12C22 14.136 21.33 16.116 20.19 17.74L17 12H20C20.0001 10.4316 19.5392 8.89781 18.6747 7.58927C17.8101 6.28072 16.5799 5.25517 15.1372 4.64013C13.6944 4.0251 12.1027 3.84771 10.56 4.13003C9.0172 4.41234 7.59145 5.14191 6.46 6.22801L5.463 4.43301ZM18.537 19.567C16.7224 21.1393 14.401 22.0034 12 22C6.477 22 2 17.523 2 12C2 9.86401 2.67 7.88401 3.81 6.26001L7 12H4C3.99987 13.5684 4.46075 15.1022 5.32534 16.4108C6.18992 17.7193 7.42007 18.7449 8.86282 19.3599C10.3056 19.9749 11.8973 20.1523 13.44 19.87C14.9828 19.5877 16.4085 18.8581 17.54 17.772L18.537 19.567Z"
></path>
</svg>
</span>
Refresh
</button>
</div>
{_users.length > 0 && (
<div className="users-wrapper">
{users.map((user, i) => (
<div key={i}>
<p className="box box-text">
{_identity?.commitment.toString() === user ? <b>{user}</b> : user}
</p>
</div>
))}
</div>
)}
<div className="join-group-button">
<button
className="button"
onClick={joinGroup}
disabled={_loading || !_identity || userHasJoined}
type="button"
>
<span>Join group</span>
{_loading && <div className="loader"></div>}
</button>
</div>
<div className="divider" />
<Stepper
step={2}
onPrevClick={() => router.push("/")}
onNextClick={userHasJoined ? () => router.push("/proofs") : undefined}
/>
</>
)
}

View File

@@ -1,142 +0,0 @@
"use client"
import { Identity } from "@semaphore-protocol/core"
import { useRouter } from "next/navigation"
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"
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 privateKey = localStorage.getItem("identity")
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(privateKey))
}, [router])
useEffect(() => {
if (_users.length > 0) {
setLogs(`${_users.length} user${_users.length > 1 ? "s" : ""} retrieved from the group 🤙🏽`)
}
}, [_users, setLogs])
const joinGroup = useCallback(async () => {
if (!_identity) {
return
}
setLoading(true)
setLogs(`Joining the Feedback group...`)
let response: any
if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) {
response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
abi: Feedback.abi,
address: process.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 have joined the Feedback group event 🎉 Share your feedback anonymously!`)
} else {
setLogs("Some error occurred, please try again!")
}
setLoading(false)
}, [_identity, addUser, setLogs])
const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment.toString()), [_users])
return (
<>
<h2>Groups</h2>
<p>
<a
href="https://docs.semaphore.pse.dev/guides/groups"
target="_blank"
rel="noreferrer noopener nofollow"
>
Semaphore groups
</a>{" "}
are{" "}
<a
href="https://zkkit.pse.dev/classes/_zk_kit_imt.LeanIMT.html"
target="_blank"
rel="noreferrer noopener nofollow"
>
Lean incremental Merkle trees
</a>{" "}
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>Group 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.toString()}</p>
</div>
))}
</div>
)}
<div className="divider"></div>
<Stepper
step={2}
onPrevClick={() => router.push("/")}
onNextClick={_identity && userHasJoined(_identity) ? () => router.push("/proofs") : undefined}
/>
</>
)
}

View File

@@ -1,8 +1,10 @@
import PageContainer from "@/components/PageContainer"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { LogContextProvider } from "@/context/LogContext"
import { SemaphoreContextProvider } from "@/context/SemaphoreContext"
import "./globals.css"
import { Inter } from "next/font/google"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
@@ -32,8 +34,20 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com"></link>
<link rel="preconnect" href="https://fonts.gstatic.com"></link>
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap"
rel="stylesheet"
></link>
</head>
<body suppressHydrationWarning className={inter.className}>
<PageContainer>{children}</PageContainer>
<SemaphoreContextProvider>
<LogContextProvider>
<PageContainer>{children}</PageContainer>
</LogContextProvider>
</SemaphoreContextProvider>
</body>
</html>
)

View File

@@ -2,42 +2,42 @@
import { Identity } from "@semaphore-protocol/core"
import { useRouter } from "next/navigation"
import { useCallback, useContext, useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import Stepper from "../components/Stepper"
import LogsContext from "../context/LogsContext"
import { useLogContext } from "../context/LogContext"
export default function IdentitiesPage() {
const router = useRouter()
const { setLogs } = useContext(LogsContext)
const { setLog } = useLogContext()
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const privateKey = localStorage.getItem("identity")
if (privateKey) {
const identity = new Identity(privateKey)
const identity = Identity.import(privateKey)
setIdentity(identity)
setLogs("Your Semaphore identity has been retrieved from the browser cache 👌🏽")
setLog("Your Semaphore identity has been retrieved from the browser cache 👌🏽")
} else {
setLogs("Create your Semaphore identity 👆🏽")
setLog("Create your Semaphore identity 👆🏽")
}
}, [setLogs])
}, [setLog])
const createIdentity = useCallback(async () => {
const identity = new Identity()
setIdentity(identity)
localStorage.setItem("identity", identity.privateKey.toString())
localStorage.setItem("identity", identity.export())
setLogs("Your new Semaphore identity has just been created 🎉")
}, [setLogs])
setLog("Your new Semaphore identity has just been created 🎉")
}, [setLog])
return (
<>
<h2 className="font-size: 3rem;">Identities</h2>
<h2>Identities</h2>
<p>
The identity of a user in the Semaphore protocol. A{" "}
@@ -59,35 +59,36 @@ export default function IdentitiesPage() {
public/private key pair and a commitment, used as the public identifier of the identity.
</p>
<div className="divider"></div>
<div className="divider" />
<div className="text-top">
<div className="keys-header">
<h3>Identity</h3>
{_identity && (
<button className="button-link" onClick={createIdentity}>
New
</button>
)}
</div>
{_identity ? (
<div>
<div className="box">
<p className="box-text">Private Key: {_identity.privateKey.toString()}</p>
<p className="box-text">Commitment: {_identity.commitment.toString()}</p>
</div>
</div>
) : (
<div>
<button className="button" onClick={createIdentity}>
Create identity
</button>
{_identity && (
<div className="key-wrapper">
<p>
<b>Private Key (base64)</b>:<br /> {_identity.export()}
</p>
<p>
<b>Public Key</b>:<br /> [{_identity.publicKey[0].toString()},{" "}
{_identity.publicKey[1].toString()}]
</p>
<p>
<b>Commitment</b>:<br /> {_identity.commitment.toString()}
</p>
</div>
)}
<div className="divider"></div>
<div>
<button className="button" onClick={createIdentity} type="button">
Create identity
</button>
</div>
<Stepper step={1} onNextClick={_identity && (() => router.push("/groups"))} />
<div className="divider" />
<Stepper step={1} onNextClick={_identity && (() => router.push("/group"))} />
</>
)
}

View File

@@ -1,78 +1,94 @@
"use client"
import { Group, Identity, generateProof } from "@semaphore-protocol/core"
import Stepper from "@/components/Stepper"
import { useLogContext } from "@/context/LogContext"
import { useSemaphoreContext } from "@/context/SemaphoreContext"
import { generateProof, Group } from "@semaphore-protocol/core"
import { encodeBytes32String, ethers } from "ethers"
import { useRouter } from "next/navigation"
import { useCallback, useContext, useEffect, useState } from "react"
import { useCallback, useEffect, useMemo, 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"
import useSemaphoreIdentity from "@/hooks/useSemaphoreIdentity"
export default function ProofsPage() {
const router = useRouter()
const { setLogs } = useContext(LogsContext)
const { _users, _feedback, refreshFeedback, addFeedback } = useContext(SemaphoreContext)
const { setLog } = useLogContext()
const { _users, _feedback, refreshFeedback, addFeedback } = useSemaphoreContext()
const [_loading, setLoading] = useState(false)
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const privateKey = localStorage.getItem("identity")
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(privateKey))
}, [router])
const { _identity } = useSemaphoreIdentity()
useEffect(() => {
if (_feedback.length > 0) {
setLogs(`${_feedback.length} feedback retrieved from the group 🤙🏽`)
setLog(`${_feedback.length} feedback retrieved from the group 🤙🏽`)
}
}, [_feedback, setLogs])
}, [_feedback, setLog])
const feedback = useMemo(() => [..._feedback].reverse(), [_feedback])
const sendFeedback = useCallback(async () => {
if (!_identity) {
return
}
if (typeof process.env.NEXT_PUBLIC_GROUP_ID !== "string") {
throw new Error("Please, define NEXT_PUBLIC_GROUP_ID in your .env file")
}
const feedback = prompt("Please enter your feedback:")
if (feedback && _users) {
setLoading(true)
setLogs(`Posting your anonymous feedback...`)
setLog(`Posting your anonymous feedback...`)
try {
const group = new Group(_users)
const { points, merkleTreeDepth, merkleTreeRoot, nullifier, message } = await generateProof(
const message = encodeBytes32String(feedback)
const { points, merkleTreeDepth, merkleTreeRoot, nullifier } = await generateProof(
_identity,
group,
feedback,
message,
process.env.NEXT_PUBLIC_GROUP_ID as string
)
let response: any
if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) {
response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, {
let feedbackSent: boolean = false
const params = [merkleTreeDepth, merkleTreeRoot, nullifier, message, points]
if (process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK) {
const response = await fetch(process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
abi: Feedback.abi,
address: process.env.FEEDBACK_CONTRACT_ADDRESS,
address: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS,
functionName: "sendFeedback",
functionParameters: [merkleTreeDepth, merkleTreeRoot, nullifier, message, points]
functionParameters: params
})
})
if (response.status === 200) {
feedbackSent = true
}
} else if (
process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT &&
process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID &&
process.env.GELATO_RELAYER_API_KEY
) {
const iface = new ethers.Interface(Feedback.abi)
const request = {
chainId: process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID,
target: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS,
data: iface.encodeFunctionData("sendFeedback", params),
sponsorApiKey: process.env.GELATO_RELAYER_API_KEY
}
const response = await fetch(process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request)
})
if (response.status === 201) {
feedbackSent = true
}
} else {
response = await fetch("api/feedback", {
const response = await fetch("api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@@ -83,24 +99,28 @@ export default function ProofsPage() {
points
})
})
if (response.status === 200) {
feedbackSent = true
}
}
if (response.status === 200) {
if (feedbackSent) {
addFeedback(feedback)
setLogs(`Your feedback has been posted 🎉`)
setLog(`Your feedback has been posted 🎉`)
} else {
setLogs("Some error occurred, please try again!")
setLog("Some error occurred, please try again!")
}
} catch (error) {
console.error(error)
setLogs("Some error occurred, please try again!")
setLog("Some error occurred, please try again!")
} finally {
setLoading(false)
}
}
}, [_identity, _users, addFeedback, setLogs])
}, [_identity, _users, addFeedback, setLoading, setLog])
return (
<>
@@ -122,22 +142,23 @@ export default function ProofsPage() {
<div className="divider"></div>
<div className="text-top">
<h3>Feedback messages ({_feedback.length})</h3>
<button className="button-link" onClick={refreshFeedback}>
<h3>Feedback ({_feedback.length})</h3>
<button className="refresh-button" onClick={refreshFeedback}>
<span className="refresh-span">
<svg viewBox="0 0 24 24" focusable="false" className="refresh-icon">
<path
fill="currentColor"
d="M5.463 4.43301C7.27756 2.86067 9.59899 1.99666 12 2.00001C17.523 2.00001 22 6.47701 22 12C22 14.136 21.33 16.116 20.19 17.74L17 12H20C20.0001 10.4316 19.5392 8.89781 18.6747 7.58927C17.8101 6.28072 16.5799 5.25517 15.1372 4.64013C13.6944 4.0251 12.1027 3.84771 10.56 4.13003C9.0172 4.41234 7.59145 5.14191 6.46 6.22801L5.463 4.43301ZM18.537 19.567C16.7224 21.1393 14.401 22.0034 12 22C6.477 22 2 17.523 2 12C2 9.86401 2.67 7.88401 3.81 6.26001L7 12H4C3.99987 13.5684 4.46075 15.1022 5.32534 16.4108C6.18992 17.7193 7.42007 18.7449 8.86282 19.3599C10.3056 19.9749 11.8973 20.1523 13.44 19.87C14.9828 19.5877 16.4085 18.8581 17.54 17.772L18.537 19.567Z"
></path>
</svg>
</span>
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) => (
{feedback.length > 0 && (
<div className="feedback-wrapper">
{feedback.map((f, i) => (
<div key={i}>
<p className="box box-text">{f}</p>
</div>
@@ -145,9 +166,16 @@ export default function ProofsPage() {
</div>
)}
<div className="send-feedback-button">
<button className="button" onClick={sendFeedback} disabled={_loading}>
<span>Send Feedback</span>
{_loading && <div className="loader"></div>}
</button>
</div>
<div className="divider"></div>
<Stepper step={3} onPrevClick={() => router.push("/groups")} />
<Stepper step={3} onPrevClick={() => router.push("/group")} />
</>
)
}

View File

@@ -1,12 +1,8 @@
"use client"
import LogsContext from "@/context/LogsContext"
import SemaphoreContext from "@/context/SemaphoreContext"
import useSemaphore from "@/hooks/useSemaphore"
import { useLogContext } from "@/context/LogContext"
import shortenString from "@/utils/shortenString"
import { SupportedNetwork } from "@semaphore-protocol/utils"
import { usePathname } from "next/navigation"
import { useEffect, useState } from "react"
import Link from "next/link"
export default function PageContainer({
@@ -15,15 +11,9 @@ export default function PageContainer({
children: React.ReactNode
}>) {
const pathname = usePathname()
const semaphore = useSemaphore()
const [_logs, setLogs] = useState<string>("")
const { log } = useLogContext()
useEffect(() => {
semaphore.refreshUsers()
semaphore.refreshFeedback()
}, [])
function getExplorerLink(network: SupportedNetwork, address: string) {
function getExplorerLink(network: string, address: string) {
switch (network) {
case "sepolia":
return `https://sepolia.etherscan.io/address/${address}`
@@ -35,7 +25,7 @@ export default function PageContainer({
}
return (
<div>
<>
<div className="header">
<Link href="/" className="header-left">
Feedback
@@ -43,7 +33,7 @@ export default function PageContainer({
<div className="header-right">
<a
href={getExplorerLink(
process.env.NEXT_PUBLIC_DEFAULT_NETWORK as SupportedNetwork,
process.env.NEXT_PUBLIC_DEFAULT_NETWORK as string,
process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string
)}
target="_blank"
@@ -52,47 +42,34 @@ export default function PageContainer({
<div>{shortenString(process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string, [6, 4])}</div>
</a>
<a
href="https://github.com/semaphore-protocol/semaphore"
href="https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-monorepo-ethers"
target="_blank"
rel="noreferrer noopener nofollow"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-github"
>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
<button type="button" className="github-button" aria-label="Github repository">
<svg
height="32"
aria-hidden="true"
viewBox="0 0 24 24"
version="1.1"
width="32"
data-view-component="true"
>
<path d="M12.5.75C6.146.75 1 5.896 1 12.25c0 5.089 3.292 9.387 7.863 10.91.575.101.79-.244.79-.546 0-.273-.014-1.178-.014-2.142-2.889.532-3.636-.704-3.866-1.35-.13-.331-.69-1.352-1.18-1.625-.402-.216-.977-.748-.014-.762.906-.014 1.553.834 1.769 1.179 1.035 1.74 2.688 1.25 3.349.948.1-.747.402-1.25.733-1.538-2.559-.287-5.232-1.279-5.232-5.678 0-1.25.445-2.285 1.178-3.09-.115-.288-.517-1.467.115-3.048 0 0 .963-.302 3.163 1.179.92-.259 1.897-.388 2.875-.388.977 0 1.955.13 2.875.388 2.2-1.495 3.162-1.179 3.162-1.179.633 1.581.23 2.76.115 3.048.733.805 1.179 1.825 1.179 3.09 0 4.413-2.688 5.39-5.247 5.678.417.36.776 1.05.776 2.128 0 1.538-.014 2.774-.014 3.162 0 .302.216.662.79.547C20.709 21.637 24 17.324 24 12.25 24 5.896 18.854.75 12.5.75Z"></path>
</svg>
</button>
</a>
</div>
</div>
<div className="container">
<SemaphoreContext.Provider value={semaphore}>
<LogsContext.Provider
value={{
_logs,
setLogs
}}
>
{children}
</LogsContext.Provider>
</SemaphoreContext.Provider>
</div>
<div className="container">{children}</div>
<div className="divider-footer"></div>
<div className="divider-footer" />
<div className="footer">
{_logs.endsWith("...")}
<p>{_logs || `Current step: ${pathname}`}</p>
{log.endsWith("...")}
<p>{log || `Current step: ${pathname}`}</p>
</div>
</div>
</>
)
}

View File

@@ -1,3 +1,5 @@
"use client"
export type StepperProps = {
step: number
onPrevClick?: () => void
@@ -8,21 +10,47 @@ export default function Stepper({ step, onPrevClick, onNextClick }: StepperProps
return (
<div className="stepper">
{onPrevClick !== undefined ? (
<button className="button-stepper" disabled={!onPrevClick} onClick={onPrevClick || undefined}>
<button
className="button-stepper"
disabled={!onPrevClick}
onClick={onPrevClick || undefined}
type="button"
>
<span className="stepper-icon left-pad">
<svg viewBox="0 0 24 24" focusable="false">
<path
fill="currentColor"
d="M16.2425 6.34317L14.8283 4.92896L7.75732 12L14.8284 19.0711L16.2426 17.6569L10.5857 12L16.2425 6.34317Z"
></path>
</svg>
</span>
Prev
</button>
) : (
<span></span>
<button className="button-stepper"></button>
)}
<p>{step.toString()}/3</p>
{onNextClick !== undefined ? (
<button className="button-stepper" disabled={!onNextClick} onClick={onNextClick || undefined}>
<button
className="button-stepper"
disabled={!onNextClick}
onClick={onNextClick || undefined}
type="button"
>
Next
<span className="stepper-icon right-pad">
<svg viewBox="0 0 24 24" focusable="false">
<path
fill="currentColor"
d="M10.5859 6.34317L12.0001 4.92896L19.0712 12L12.0001 19.0711L10.5859 17.6569L16.2428 12L10.5859 6.34317Z"
></path>
</svg>
</span>
</button>
) : (
<span></span>
<span className="button-stepper" />
)}
</div>
)

View File

@@ -0,0 +1,37 @@
"use client"
import React, { createContext, ReactNode, useContext, useState } from "react"
export type LogContextType = {
log: string
setLog: (logs: string) => void
}
const LogContext = createContext<LogContextType | null>(null)
interface ProviderProps {
children: ReactNode
}
export const LogContextProvider: React.FC<ProviderProps> = ({ children }) => {
const [log, setLog] = useState<string>("")
return (
<LogContext.Provider
value={{
log,
setLog
}}
>
{children}
</LogContext.Provider>
)
}
export const useLogContext = () => {
const context = useContext(LogContext)
if (context === null) {
throw new Error("LogContext must be used within a LogContextProvider")
}
return context
}

View File

@@ -1,11 +0,0 @@
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

@@ -1,19 +0,0 @@
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,94 @@
"use client"
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from "react"
import { SemaphoreEthers } from "@semaphore-protocol/data"
import { decodeBytes32String, toBeHex } from "ethers"
export type SemaphoreContextType = {
_users: string[]
_feedback: string[]
refreshUsers: () => Promise<void>
addUser: (user: string) => void
refreshFeedback: () => Promise<void>
addFeedback: (feedback: string) => void
}
const SemaphoreContext = createContext<SemaphoreContextType | null>(null)
interface ProviderProps {
children: ReactNode
}
const ethereumNetwork =
process.env.NEXT_PUBLIC_DEFAULT_NETWORK === "localhost"
? "http://127.0.0.1:8545"
: process.env.NEXT_PUBLIC_DEFAULT_NETWORK
export const SemaphoreContextProvider: React.FC<ProviderProps> = ({ children }) => {
const [_users, setUsers] = useState<any[]>([])
const [_feedback, setFeedback] = useState<string[]>([])
const refreshUsers = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreEthers(ethereumNetwork, {
address: process.env.NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS,
projectId: process.env.NEXT_PUBLIC_INFURA_API_KEY
})
const members = await semaphore.getGroupMembers(process.env.NEXT_PUBLIC_GROUP_ID as string)
setUsers(members.map((member) => member.toString()))
}, [])
const addUser = useCallback(
(user: any) => {
setUsers([..._users, user])
},
[_users]
)
const refreshFeedback = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreEthers(ethereumNetwork, {
address: process.env.NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS,
projectId: process.env.NEXT_PUBLIC_INFURA_API_KEY
})
const proofs = await semaphore.getGroupValidatedProofs(process.env.NEXT_PUBLIC_GROUP_ID as string)
setFeedback(proofs.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32))))
}, [])
const addFeedback = useCallback(
(feedback: string) => {
setFeedback([..._feedback, feedback])
},
[_feedback]
)
useEffect(() => {
refreshUsers()
refreshFeedback()
}, [refreshFeedback, refreshUsers])
return (
<SemaphoreContext.Provider
value={{
_users,
_feedback,
refreshUsers,
addUser,
refreshFeedback,
addFeedback
}}
>
{children}
</SemaphoreContext.Provider>
)
}
export const useSemaphoreContext = () => {
const context = useContext(SemaphoreContext)
if (context === null) {
throw new Error("SemaphoreContext must be used within a SemaphoreContextProvider")
}
return context
}

View File

@@ -1,57 +0,0 @@
import { SemaphoreEthers } from "@semaphore-protocol/data"
import { decodeBytes32String, toBeHex } from "ethers"
import { useCallback, useState } from "react"
import { SemaphoreContextType } from "../context/SemaphoreContext"
const ethereumNetwork =
process.env.NEXT_PUBLIC_DEFAULT_NETWORK === "localhost"
? "http://127.0.0.1:8545"
: process.env.NEXT_PUBLIC_DEFAULT_NETWORK
export default function useSemaphore(): SemaphoreContextType {
const [_users, setUsers] = useState<string[]>([])
const [_feedback, setFeedback] = useState<string[]>([])
const refreshUsers = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreEthers(ethereumNetwork, {
address: process.env.NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS
})
const members = await semaphore.getGroupMembers(process.env.NEXT_PUBLIC_GROUP_ID as string)
setUsers(members)
}, [])
const addUser = useCallback(
(user: any) => {
setUsers([..._users, user])
},
[_users]
)
const refreshFeedback = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreEthers(ethereumNetwork, {
address: process.env.NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS
})
const proofs = await semaphore.getGroupValidatedProofs(process.env.NEXT_PUBLIC_GROUP_ID as string)
setFeedback(proofs.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32))))
}, [])
const addFeedback = useCallback(
(feedback: string) => {
setFeedback([..._feedback, feedback])
},
[_feedback]
)
return {
_users,
_feedback,
refreshUsers,
addUser,
refreshFeedback,
addFeedback
}
}

View File

@@ -0,0 +1,23 @@
import { useEffect, useState } from "react"
import { Identity } from "@semaphore-protocol/core"
import { useRouter } from "next/navigation"
export default function useSemaphoreIdentity() {
const router = useRouter()
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const privateKey = localStorage.getItem("identity")
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(privateKey))
}, [router])
return {
_identity
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/cli-template-monorepo-ethers",
"version": "4.0.2",
"version": "4.6.0",
"description": "Semaphore Hardhat + Next.js + SemaphoreEthers template.",
"license": "Unlicense",
"files": [

View File

@@ -20,9 +20,9 @@
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@semaphore-protocol/core": "4.0.2",
"@semaphore-protocol/hardhat": "4.0.2",
"@semaphore-protocol/utils": "4.0.2",
"@semaphore-protocol/core": "4.6.0",
"@semaphore-protocol/hardhat": "4.6.0",
"@semaphore-protocol/utils": "4.6.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
@@ -50,7 +50,7 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@semaphore-protocol/contracts": "4.0.2"
"@semaphore-protocol/contracts": "4.6.0"
},
"packageManager": "yarn@4.1.0"
}

View File

@@ -1,4 +1,4 @@
NEXT_PUBLIC_DEFAULT_NETWORK=localhost
NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9
NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
NEXT_PUBLIC_GROUP_ID=0

View File

@@ -1,5 +1,8 @@
NEXT_PUBLIC_DEFAULT_NETWORK=sepolia
NEXT_PUBLIC_INFURA_API_KEY=abf67af1010b4b8d877e04244f1eac3d
NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=
NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x1e0d7FF1610e480fC93BdEC510811ea2Ba6d7c2f
NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK=https://api.defender.openzeppelin.com/actions/20fce2ae-844b-4ec0-a6a2-90a3350a9d2c/runs/webhook/303216d1-fa7d-4fca-8c5b-7ba1ba544fc7/2T7i9xrkZA5j37hoaQLUuw
NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT=https://api.gelato.digital/relays/v2/sponsored-call
NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID=11155111
NEXT_PUBLIC_GROUP_ID=

View File

@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# testing
/coverage
# next.js
/.next/
/out/
@@ -7,3 +10,18 @@
# production
/build
# misc
.DS_Store
*.pem
# typescript
*.tsbuildinfo
next-env.d.ts
# 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

@@ -9,11 +9,6 @@
"internalType": "address",
"name": "semaphoreAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "_groupId",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
@@ -92,8 +87,8 @@
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b5060405161083f38038061083f833981810160405281019061003291906101a8565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c96e71fb600154306040518363ffffffff1660e01b81526004016100d6929190610206565b600060405180830381600087803b1580156100f057600080fd5b505af1158015610104573d6000803e3d6000fd5b50505050505061022f565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061013f82610114565b9050919050565b61014f81610134565b811461015a57600080fd5b50565b60008151905061016c81610146565b92915050565b6000819050919050565b61018581610172565b811461019057600080fd5b50565b6000815190506101a28161017c565b92915050565b600080604083850312156101bf576101be61010f565b5b60006101cd8582860161015d565b92505060206101de85828601610193565b9150509250929050565b6101f181610172565b82525050565b61020081610134565b82525050565b600060408201905061021b60008301856101e8565b61022860208301846101f7565b9392505050565b6106018061023e6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033",
"bytecode": "0x608060405234801561001057600080fd5b506040516108473803806108478339818101604052810190610032919061017d565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635c3f3b60306040518263ffffffff1660e01b81526004016100cb91906101b9565b6020604051808303816000875af11580156100ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061010e919061020a565b60018190555050610237565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061014a8261011f565b9050919050565b61015a8161013f565b811461016557600080fd5b50565b60008151905061017781610151565b92915050565b6000602082840312156101935761019261011a565b5b60006101a184828501610168565b91505092915050565b6101b38161013f565b82525050565b60006020820190506101ce60008301846101aa565b92915050565b6000819050919050565b6101e7816101d4565b81146101f257600080fd5b50565b600081519050610204816101de565b92915050565b6000602082840312156102205761021f61011a565b5b600061022e848285016101f5565b91505092915050565b610601806102466000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea264697066735822122078569abd7f309f3107c4d19e9b4a4f4812522ccc5dc57c7ccbe2b06a5ba461b064736f6c63430008170033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea264697066735822122078569abd7f309f3107c4d19e9b4a4f4812522ccc5dc57c7ccbe2b06a5ba461b064736f6c63430008170033",
"linkReferences": {},
"deployedLinkReferences": {}
}

View File

@@ -14,7 +14,8 @@ const nextConfig = withPWA({
})({
env: {
INFURA_API_KEY: process.env.INFURA_API_KEY,
ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY
ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY,
GELATO_RELAYER_API_KEY: process.env.GELATO_RELAYER_API_KEY
}
})

View File

@@ -9,9 +9,9 @@
"lint": "next lint"
},
"dependencies": {
"@semaphore-protocol/core": "4.0.2",
"@semaphore-protocol/data": "4.0.2",
"@semaphore-protocol/utils": "4.0.2",
"@semaphore-protocol/core": "4.6.0",
"@semaphore-protocol/data": "4.6.0",
"@semaphore-protocol/utils": "4.6.0",
"ethers": "^6.11.1",
"next": "14.1.0",
"next-pwa": "^5.6.0",

View File

@@ -3,26 +3,14 @@ import { NextRequest } from "next/server"
import Feedback from "../../../../contract-artifacts/Feedback.json"
export async function POST(req: NextRequest) {
if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") {
throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file")
}
if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") {
throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file")
}
if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") {
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.NEXT_PUBLIC_DEFAULT_NETWORK
const infuraApiKey = process.env.INFURA_API_KEY
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS
const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK as string
const infuraApiKey = process.env.NEXT_PUBLIC_INFURA_API_KEY as string
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string
const provider =
ethereumNetwork === "localhost"

View File

@@ -3,26 +3,14 @@ import { NextRequest } from "next/server"
import Feedback from "../../../../contract-artifacts/Feedback.json"
export async function POST(req: NextRequest) {
if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") {
throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file")
}
if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") {
throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file")
}
if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") {
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.NEXT_PUBLIC_DEFAULT_NETWORK
const infuraApiKey = process.env.INFURA_API_KEY
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS
const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK as string
const infuraApiKey = process.env.NEXT_PUBLIC_INFURA_API_KEY as string
const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string
const provider =
ethereumNetwork === "localhost"

View File

@@ -1,29 +1,40 @@
:root {
--slate100: #f1f5f9;
--slate200: #e2e8f0;
--slate300: #cbd5e1;
--slate400: #94a3b8;
--slate500: #64748b;
--slate700: #334155;
--blue200: #bfdbfe;
--blue500: #3b82f6;
--blue600: #2563eb;
--blue700: #1d4ed8;
--blue800: #1e40af;
--blue900: #1e3a8a;
--blue100: #dde6fc;
--blue200: #c3d4fa;
--blue300: #9abaf6;
--blue400: #6a95f0;
--blue500: #4771ea;
--blue600: #3555df;
--blue700: #2940cc;
--blue800: #2735a6;
--blue900: #253183;
--blue950: #1b2050;
--darkBlueBg: #00020d;
--slate50: #f7f7f8;
--slate100: #eeeef0;
--slate200: #d9d9de;
--slate300: #b8b9c1;
--slate400: #92939e;
--slate500: #747583;
--slate600: #5e5f6b;
--slate700: #4d4e57;
--slate800: #42424a;
--slate900: #3a3a40;
--slate950: #26262b;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Outfit", sans-serif;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
height: 100vh;
}
body {
@@ -33,17 +44,30 @@ body {
p {
line-height: 1.5rem;
font-weight: 300;
overflow-wrap: break-word;
font-size: 1rem;
}
b {
font-weight: 600;
}
.key-wrapper {
padding-bottom: 1.5rem;
padding-left: 0.5rem;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
max-width: 32rem;
max-width: 40vw;
min-width: 35rem;
margin: auto;
padding: 1rem;
height: calc(100vh - 7rem - 1px);
padding-bottom: 5rem;
min-height: calc(100vh - 3.5rem);
}
@@ -57,28 +81,25 @@ p {
margin-bottom: 1rem;
}
ol {
padding: 1rem;
}
li {
margin-top: 1rem;
}
h2 {
font-size: 2.25rem;
font-weight: 500;
margin-bottom: 1rem;
line-height: 1.2;
}
h3 {
font-size: 1.125rem;
font-size: 1.15rem;
font-weight: 500;
}
.divider {
height: 1px;
background: var(--slate500);
opacity: 0.6;
border: 0;
border-style: solid;
border-bottom-width: 1px;
width: 100%;
border-color: var(--slate400);
margin: 2rem 0;
}
@@ -89,31 +110,44 @@ h3 {
}
a {
color: var(--blue500);
color: var(--blue400);
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration-color: var(--blue400);
}
.button {
background-color: var(--blue800);
width: 100%;
padding: 0.8rem 1rem;
border: 1px;
border-radius: 100px;
cursor: pointer;
color: var(--slate100);
font-size: 1.125rem;
font-weight: 500;
transition: all 200ms linear;
margin-top: 1rem;
margin-bottom: 1.5rem;
opacity: 0.9;
font-weight: 400;
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-duration: 200ms;
padding-left: 18px !important;
padding-right: 18px !important;
height: 2.5rem;
min-width: 2.5rem;
font-size: 1rem;
-webkit-padding-start: 1rem;
padding-inline-start: 1rem;
-webkit-padding-end: 1rem;
padding-inline-end: 1rem;
background: var(--blue500);
color: white;
background-image: linear-gradient(to right, var(--blue500), var(--blue800));
transition-timing-function: linear;
width: 100%;
border: none;
display: flex;
justify-content: center;
height: 3rem;
align-items: center;
justify-content: center;
}
.button:hover {
background-color: var(--blue900);
background-color: var(--blue800);
background-image: none;
cursor: pointer;
}
.button:disabled {
@@ -123,54 +157,126 @@ a {
.button:disabled:hover {
background-color: var(--blue800);
background-image: none;
}
.button-stepper {
cursor: pointer;
color: var(--blue600);
color: var(--blue500);
font-size: 1.1rem;
border: none;
background: none;
width: 4rem;
margin: 0 1rem;
display: flex;
justify-content: center;
}
.button-link {
.button-stepper:hover {
text-decoration: underline;
}
.refresh-wrapper {
color: var(--slate400);
display: flex;
align-items: center;
}
.refresh-wrapper:hover {
text-decoration: underline;
}
.refresh-button {
cursor: pointer;
color: var(--slate300);
font-size: 1.1rem;
border: none;
background: none;
padding-right: 1rem;
display: flex;
align-items: center;
}
.refresh-button:hover {
text-decoration: underline;
}
.refresh-span {
width: 1.5em;
height: 1em;
display: inline-block;
line-height: 1em;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: currentColor;
vertical-align: middle;
}
.refresh-icon {
margin-inline-end: 0.5rem;
}
.stepper-icon {
display: inline-flex;
align-self: center;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.left-pad {
margin-inline-end: 0.5rem;
}
.right-pad {
margin-inline-start: 0.5rem;
}
.stepper-icon svg {
width: 1em;
height: 1em;
display: inline-block;
line-height: 1em;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: currentColor;
vertical-align: super;
}
.box {
padding: 0.8rem;
border-style: solid;
border-width: 1px;
border-color: var(--slate500);
border-radius: 4px;
border-bottom: 1px solid white;
padding: 0.8rem 0;
max-height: 50px;
}
.box-text {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0.5rem;
font-size: 1rem;
}
.header {
display: flex;
justify-content: space-between;
padding: 1.5rem;
padding: 0 1.5rem;
height: 3.5rem;
}
.header-left {
font-size: 1.125rem;
font-weight: 500;
text-decoration: none;
text-decoration: none !important;
display: flex;
align-items: center;
}
.header-right {
display: flex;
gap: 1.5rem;
align-items: center;
}
.footer {
@@ -188,6 +294,57 @@ a {
background: var(--slate500);
}
.users-wrapper,
.feedback-wrapper {
max-height: 300px;
overflow-y: scroll;
}
.keys-header {
margin-bottom: 1.5rem;
}
.users-header {
font-weight: 700;
font-size: 1.125rem;
font-family: "DM Sans", sans-serif;
}
.join-group-button,
.send-feedback-button {
margin-top: 1.5rem;
}
.github-icon {
width: 1.5rem;
height: 1.5rem;
display: inline-block;
line-height: 1em;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: currentColor;
}
.github-button {
cursor: pointer;
color: var(--slate100);
font-size: 1.1rem;
border: none;
background: none;
padding-right: 1rem;
display: flex;
align-items: center;
box-sizing: border-box;
display: inline-block;
overflow: visible !important;
fill: currentColor;
color: #f0f6fc !important;
vertical-align: middle !important;
width: 2rem;
height: 2rem;
}
.loader {
width: 25px;
height: 25px;
@@ -196,7 +353,7 @@ a {
border-right: 2px solid transparent;
animation: spin 1s linear infinite;
z-index: 20;
margin-left: 1rem;
margin-left: 0.5rem;
}
@keyframes spin {

View File

@@ -0,0 +1,177 @@
"use client"
import Stepper from "@/components/Stepper"
import { useLogContext } from "@/context/LogContext"
import { useSemaphoreContext } from "@/context/SemaphoreContext"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useMemo } from "react"
import Feedback from "../../../contract-artifacts/Feedback.json"
import { ethers } from "ethers"
import useSemaphoreIdentity from "@/hooks/useSemaphoreIdentity"
import { useState } from "react"
export default function GroupsPage() {
const router = useRouter()
const { setLog } = useLogContext()
const { _users, refreshUsers, addUser } = useSemaphoreContext()
const [_loading, setLoading] = useState(false)
const { _identity } = useSemaphoreIdentity()
useEffect(() => {
if (_users.length > 0) {
setLog(`${_users.length} user${_users.length > 1 ? "s" : ""} retrieved from the group 🤙🏽`)
}
}, [_users, setLog])
const users = useMemo(() => [..._users].reverse(), [_users])
const joinGroup = useCallback(async () => {
if (!_identity) {
return
}
setLoading(true)
setLog(`Joining the Feedback group...`)
let joinedGroup: boolean = false
if (process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK) {
const response = await fetch(process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
abi: Feedback.abi,
address: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string,
functionName: "joinGroup",
functionParameters: [_identity.commitment.toString()]
})
})
if (response.status === 200) {
joinedGroup = true
}
} else if (
process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT &&
process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID &&
process.env.GELATO_RELAYER_API_KEY
) {
const iface = new ethers.Interface(Feedback.abi)
const request = {
chainId: process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID,
target: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS,
data: iface.encodeFunctionData("joinGroup", [_identity.commitment.toString()]),
sponsorApiKey: process.env.GELATO_RELAYER_API_KEY
}
const response = await fetch(process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request)
})
if (response.status === 201) {
joinedGroup = true
}
} else {
const response = await fetch("api/join", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
identityCommitment: _identity.commitment.toString()
})
})
if (response.status === 200) {
joinedGroup = true
}
}
if (joinedGroup) {
addUser(_identity.commitment.toString())
setLog(`You have joined the Feedback group event 🎉 Share your feedback anonymously!`)
} else {
setLog("Some error occurred, please try again!")
}
setLoading(false)
}, [_identity, addUser, setLoading, setLog])
const userHasJoined = useMemo(
() => _identity !== undefined && _users.includes(_identity.commitment.toString()),
[_identity, _users]
)
return (
<>
<h2>Groups</h2>
<p>
<a
href="https://docs.semaphore.pse.dev/guides/groups"
target="_blank"
rel="noreferrer noopener nofollow"
>
Semaphore groups
</a>{" "}
are{" "}
<a
href="https://zkkit.pse.dev/classes/_zk_kit_imt.LeanIMT.html"
target="_blank"
rel="noreferrer noopener nofollow"
>
Lean incremental Merkle trees
</a>{" "}
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 className="users-header">Group users ({_users.length})</h3>
<button className="refresh-button" onClick={refreshUsers}>
<span className="refresh-span">
<svg viewBox="0 0 24 24" focusable="false" className="refresh-icon">
<path
fill="currentColor"
d="M5.463 4.43301C7.27756 2.86067 9.59899 1.99666 12 2.00001C17.523 2.00001 22 6.47701 22 12C22 14.136 21.33 16.116 20.19 17.74L17 12H20C20.0001 10.4316 19.5392 8.89781 18.6747 7.58927C17.8101 6.28072 16.5799 5.25517 15.1372 4.64013C13.6944 4.0251 12.1027 3.84771 10.56 4.13003C9.0172 4.41234 7.59145 5.14191 6.46 6.22801L5.463 4.43301ZM18.537 19.567C16.7224 21.1393 14.401 22.0034 12 22C6.477 22 2 17.523 2 12C2 9.86401 2.67 7.88401 3.81 6.26001L7 12H4C3.99987 13.5684 4.46075 15.1022 5.32534 16.4108C6.18992 17.7193 7.42007 18.7449 8.86282 19.3599C10.3056 19.9749 11.8973 20.1523 13.44 19.87C14.9828 19.5877 16.4085 18.8581 17.54 17.772L18.537 19.567Z"
></path>
</svg>
</span>
Refresh
</button>
</div>
{_users.length > 0 && (
<div className="users-wrapper">
{users.map((user, i) => (
<div key={i}>
<p className="box box-text">
{_identity?.commitment.toString() === user ? <b>{user}</b> : user}
</p>
</div>
))}
</div>
)}
<div className="join-group-button">
<button
className="button"
onClick={joinGroup}
disabled={_loading || !_identity || userHasJoined}
type="button"
>
<span>Join group</span>
{_loading && <div className="loader"></div>}
</button>
</div>
<div className="divider" />
<Stepper
step={2}
onPrevClick={() => router.push("/")}
onNextClick={userHasJoined ? () => router.push("/proofs") : undefined}
/>
</>
)
}

View File

@@ -1,8 +1,10 @@
import PageContainer from "@/components/PageContainer"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { LogContextProvider } from "@/context/LogContext"
import { SemaphoreContextProvider } from "@/context/SemaphoreContext"
import "./globals.css"
import { Inter } from "next/font/google"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
@@ -32,8 +34,20 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com"></link>
<link rel="preconnect" href="https://fonts.gstatic.com"></link>
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap"
rel="stylesheet"
></link>
</head>
<body suppressHydrationWarning className={inter.className}>
<PageContainer>{children}</PageContainer>
<SemaphoreContextProvider>
<LogContextProvider>
<PageContainer>{children}</PageContainer>
</LogContextProvider>
</SemaphoreContextProvider>
</body>
</html>
)

View File

@@ -2,42 +2,42 @@
import { Identity } from "@semaphore-protocol/core"
import { useRouter } from "next/navigation"
import { useCallback, useContext, useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import Stepper from "../components/Stepper"
import LogsContext from "../context/LogsContext"
import { useLogContext } from "../context/LogContext"
export default function IdentitiesPage() {
const router = useRouter()
const { setLogs } = useContext(LogsContext)
const { setLog } = useLogContext()
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const privateKey = localStorage.getItem("identity")
if (privateKey) {
const identity = new Identity(privateKey)
const identity = Identity.import(privateKey)
setIdentity(identity)
setLogs("Your Semaphore identity has been retrieved from the browser cache 👌🏽")
setLog("Your Semaphore identity has been retrieved from the browser cache 👌🏽")
} else {
setLogs("Create your Semaphore identity 👆🏽")
setLog("Create your Semaphore identity 👆🏽")
}
}, [setLogs])
}, [setLog])
const createIdentity = useCallback(async () => {
const identity = new Identity()
setIdentity(identity)
localStorage.setItem("identity", identity.privateKey.toString())
localStorage.setItem("identity", identity.export())
setLogs("Your new Semaphore identity has just been created 🎉")
}, [setLogs])
setLog("Your new Semaphore identity has just been created 🎉")
}, [setLog])
return (
<>
<h2 className="font-size: 3rem;">Identities</h2>
<h2>Identities</h2>
<p>
The identity of a user in the Semaphore protocol. A{" "}
@@ -59,35 +59,36 @@ export default function IdentitiesPage() {
public/private key pair and a commitment, used as the public identifier of the identity.
</p>
<div className="divider"></div>
<div className="divider" />
<div className="text-top">
<div className="keys-header">
<h3>Identity</h3>
{_identity && (
<button className="button-link" onClick={createIdentity}>
New
</button>
)}
</div>
{_identity ? (
<div>
<div className="box">
<p className="box-text">Private Key: {_identity.privateKey.toString()}</p>
<p className="box-text">Commitment: {_identity.commitment.toString()}</p>
</div>
</div>
) : (
<div>
<button className="button" onClick={createIdentity}>
Create identity
</button>
{_identity && (
<div className="key-wrapper">
<p>
<b>Private Key (base64)</b>:<br /> {_identity.export()}
</p>
<p>
<b>Public Key</b>:<br /> [{_identity.publicKey[0].toString()},{" "}
{_identity.publicKey[1].toString()}]
</p>
<p>
<b>Commitment</b>:<br /> {_identity.commitment.toString()}
</p>
</div>
)}
<div className="divider"></div>
<div>
<button className="button" onClick={createIdentity} type="button">
Create identity
</button>
</div>
<Stepper step={1} onNextClick={_identity && (() => router.push("/groups"))} />
<div className="divider" />
<Stepper step={1} onNextClick={_identity && (() => router.push("/group"))} />
</>
)
}

View File

@@ -1,78 +1,94 @@
"use client"
import { Group, Identity, generateProof } from "@semaphore-protocol/core"
import Stepper from "@/components/Stepper"
import { useLogContext } from "@/context/LogContext"
import { useSemaphoreContext } from "@/context/SemaphoreContext"
import { generateProof, Group } from "@semaphore-protocol/core"
import { encodeBytes32String, ethers } from "ethers"
import { useRouter } from "next/navigation"
import { useCallback, useContext, useEffect, useState } from "react"
import { useCallback, useEffect, useMemo, 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"
import useSemaphoreIdentity from "@/hooks/useSemaphoreIdentity"
export default function ProofsPage() {
const router = useRouter()
const { setLogs } = useContext(LogsContext)
const { _users, _feedback, refreshFeedback, addFeedback } = useContext(SemaphoreContext)
const { setLog } = useLogContext()
const { _users, _feedback, refreshFeedback, addFeedback } = useSemaphoreContext()
const [_loading, setLoading] = useState(false)
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const privateKey = localStorage.getItem("identity")
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(privateKey))
}, [router])
const { _identity } = useSemaphoreIdentity()
useEffect(() => {
if (_feedback.length > 0) {
setLogs(`${_feedback.length} feedback retrieved from the group 🤙🏽`)
setLog(`${_feedback.length} feedback retrieved from the group 🤙🏽`)
}
}, [_feedback, setLogs])
}, [_feedback, setLog])
const feedback = useMemo(() => [..._feedback].reverse(), [_feedback])
const sendFeedback = useCallback(async () => {
if (!_identity) {
return
}
if (typeof process.env.NEXT_PUBLIC_GROUP_ID !== "string") {
throw new Error("Please, define NEXT_PUBLIC_GROUP_ID in your .env file")
}
const feedback = prompt("Please enter your feedback:")
if (feedback && _users) {
setLoading(true)
setLogs(`Posting your anonymous feedback...`)
setLog(`Posting your anonymous feedback...`)
try {
const group = new Group(_users)
const { points, merkleTreeDepth, merkleTreeRoot, nullifier, message } = await generateProof(
const message = encodeBytes32String(feedback)
const { points, merkleTreeDepth, merkleTreeRoot, nullifier } = await generateProof(
_identity,
group,
feedback,
message,
process.env.NEXT_PUBLIC_GROUP_ID as string
)
let response: any
if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) {
response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, {
let feedbackSent: boolean = false
const params = [merkleTreeDepth, merkleTreeRoot, nullifier, message, points]
if (process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK) {
const response = await fetch(process.env.NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
abi: Feedback.abi,
address: process.env.FEEDBACK_CONTRACT_ADDRESS,
address: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS,
functionName: "sendFeedback",
functionParameters: [merkleTreeDepth, merkleTreeRoot, nullifier, message, points]
functionParameters: params
})
})
if (response.status === 200) {
feedbackSent = true
}
} else if (
process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT &&
process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID &&
process.env.GELATO_RELAYER_API_KEY
) {
const iface = new ethers.Interface(Feedback.abi)
const request = {
chainId: process.env.NEXT_PUBLIC_GELATO_RELAYER_CHAIN_ID,
target: process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS,
data: iface.encodeFunctionData("sendFeedback", params),
sponsorApiKey: process.env.GELATO_RELAYER_API_KEY
}
const response = await fetch(process.env.NEXT_PUBLIC_GELATO_RELAYER_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request)
})
if (response.status === 201) {
feedbackSent = true
}
} else {
response = await fetch("api/feedback", {
const response = await fetch("api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@@ -83,24 +99,28 @@ export default function ProofsPage() {
points
})
})
if (response.status === 200) {
feedbackSent = true
}
}
if (response.status === 200) {
if (feedbackSent) {
addFeedback(feedback)
setLogs(`Your feedback has been posted 🎉`)
setLog(`Your feedback has been posted 🎉`)
} else {
setLogs("Some error occurred, please try again!")
setLog("Some error occurred, please try again!")
}
} catch (error) {
console.error(error)
setLogs("Some error occurred, please try again!")
setLog("Some error occurred, please try again!")
} finally {
setLoading(false)
}
}
}, [_identity, _users, addFeedback, setLogs])
}, [_identity, _users, addFeedback, setLoading, setLog])
return (
<>
@@ -122,22 +142,23 @@ export default function ProofsPage() {
<div className="divider"></div>
<div className="text-top">
<h3>Feedback messages ({_feedback.length})</h3>
<button className="button-link" onClick={refreshFeedback}>
<h3>Feedback ({_feedback.length})</h3>
<button className="refresh-button" onClick={refreshFeedback}>
<span className="refresh-span">
<svg viewBox="0 0 24 24" focusable="false" className="refresh-icon">
<path
fill="currentColor"
d="M5.463 4.43301C7.27756 2.86067 9.59899 1.99666 12 2.00001C17.523 2.00001 22 6.47701 22 12C22 14.136 21.33 16.116 20.19 17.74L17 12H20C20.0001 10.4316 19.5392 8.89781 18.6747 7.58927C17.8101 6.28072 16.5799 5.25517 15.1372 4.64013C13.6944 4.0251 12.1027 3.84771 10.56 4.13003C9.0172 4.41234 7.59145 5.14191 6.46 6.22801L5.463 4.43301ZM18.537 19.567C16.7224 21.1393 14.401 22.0034 12 22C6.477 22 2 17.523 2 12C2 9.86401 2.67 7.88401 3.81 6.26001L7 12H4C3.99987 13.5684 4.46075 15.1022 5.32534 16.4108C6.18992 17.7193 7.42007 18.7449 8.86282 19.3599C10.3056 19.9749 11.8973 20.1523 13.44 19.87C14.9828 19.5877 16.4085 18.8581 17.54 17.772L18.537 19.567Z"
></path>
</svg>
</span>
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) => (
{feedback.length > 0 && (
<div className="feedback-wrapper">
{feedback.map((f, i) => (
<div key={i}>
<p className="box box-text">{f}</p>
</div>
@@ -145,9 +166,16 @@ export default function ProofsPage() {
</div>
)}
<div className="send-feedback-button">
<button className="button" onClick={sendFeedback} disabled={_loading}>
<span>Send Feedback</span>
{_loading && <div className="loader"></div>}
</button>
</div>
<div className="divider"></div>
<Stepper step={3} onPrevClick={() => router.push("/groups")} />
<Stepper step={3} onPrevClick={() => router.push("/group")} />
</>
)
}

View File

@@ -0,0 +1,13 @@
"use client"
import { CacheProvider } from "@chakra-ui/next-js"
import { ChakraProvider } from "@chakra-ui/react"
import theme from "../styles/index"
export default function Providers({ children }: { children: React.ReactNode }) {
return (
<CacheProvider>
<ChakraProvider theme={theme}>{children}</ChakraProvider>
</CacheProvider>
)
}

View File

@@ -1,12 +1,8 @@
"use client"
import LogsContext from "@/context/LogsContext"
import SemaphoreContext from "@/context/SemaphoreContext"
import useSemaphore from "@/hooks/useSemaphore"
import { useLogContext } from "@/context/LogContext"
import shortenString from "@/utils/shortenString"
import { SupportedNetwork } from "@semaphore-protocol/utils"
import { usePathname } from "next/navigation"
import { useEffect, useState } from "react"
import Link from "next/link"
export default function PageContainer({
@@ -15,15 +11,9 @@ export default function PageContainer({
children: React.ReactNode
}>) {
const pathname = usePathname()
const semaphore = useSemaphore()
const [_logs, setLogs] = useState<string>("")
const { log } = useLogContext()
useEffect(() => {
semaphore.refreshUsers()
semaphore.refreshFeedback()
}, [])
function getExplorerLink(network: SupportedNetwork, address: string) {
function getExplorerLink(network: string, address: string) {
switch (network) {
case "sepolia":
return `https://sepolia.etherscan.io/address/${address}`
@@ -35,7 +25,7 @@ export default function PageContainer({
}
return (
<div>
<>
<div className="header">
<Link href="/" className="header-left">
Feedback
@@ -43,7 +33,7 @@ export default function PageContainer({
<div className="header-right">
<a
href={getExplorerLink(
process.env.NEXT_PUBLIC_DEFAULT_NETWORK as SupportedNetwork,
process.env.NEXT_PUBLIC_DEFAULT_NETWORK as string,
process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string
)}
target="_blank"
@@ -52,47 +42,34 @@ export default function PageContainer({
<div>{shortenString(process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS as string, [6, 4])}</div>
</a>
<a
href="https://github.com/semaphore-protocol/semaphore"
href="https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-monorepo-subgraph"
target="_blank"
rel="noreferrer noopener nofollow"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-github"
>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
<button type="button" className="github-button" aria-label="Github repository">
<svg
height="32"
aria-hidden="true"
viewBox="0 0 24 24"
version="1.1"
width="32"
data-view-component="true"
>
<path d="M12.5.75C6.146.75 1 5.896 1 12.25c0 5.089 3.292 9.387 7.863 10.91.575.101.79-.244.79-.546 0-.273-.014-1.178-.014-2.142-2.889.532-3.636-.704-3.866-1.35-.13-.331-.69-1.352-1.18-1.625-.402-.216-.977-.748-.014-.762.906-.014 1.553.834 1.769 1.179 1.035 1.74 2.688 1.25 3.349.948.1-.747.402-1.25.733-1.538-2.559-.287-5.232-1.279-5.232-5.678 0-1.25.445-2.285 1.178-3.09-.115-.288-.517-1.467.115-3.048 0 0 .963-.302 3.163 1.179.92-.259 1.897-.388 2.875-.388.977 0 1.955.13 2.875.388 2.2-1.495 3.162-1.179 3.162-1.179.633 1.581.23 2.76.115 3.048.733.805 1.179 1.825 1.179 3.09 0 4.413-2.688 5.39-5.247 5.678.417.36.776 1.05.776 2.128 0 1.538-.014 2.774-.014 3.162 0 .302.216.662.79.547C20.709 21.637 24 17.324 24 12.25 24 5.896 18.854.75 12.5.75Z"></path>
</svg>
</button>
</a>
</div>
</div>
<div className="container">
<SemaphoreContext.Provider value={semaphore}>
<LogsContext.Provider
value={{
_logs,
setLogs
}}
>
{children}
</LogsContext.Provider>
</SemaphoreContext.Provider>
</div>
<div className="container">{children}</div>
<div className="divider-footer"></div>
<div className="divider-footer" />
<div className="footer">
{_logs.endsWith("...")}
<p>{_logs || `Current step: ${pathname}`}</p>
{log.endsWith("...")}
<p>{log || `Current step: ${pathname}`}</p>
</div>
</div>
</>
)
}

View File

@@ -1,3 +1,5 @@
"use client"
export type StepperProps = {
step: number
onPrevClick?: () => void
@@ -8,21 +10,47 @@ export default function Stepper({ step, onPrevClick, onNextClick }: StepperProps
return (
<div className="stepper">
{onPrevClick !== undefined ? (
<button className="button-stepper" disabled={!onPrevClick} onClick={onPrevClick || undefined}>
<button
className="button-stepper"
disabled={!onPrevClick}
onClick={onPrevClick || undefined}
type="button"
>
<span className="stepper-icon left-pad">
<svg viewBox="0 0 24 24" focusable="false">
<path
fill="currentColor"
d="M16.2425 6.34317L14.8283 4.92896L7.75732 12L14.8284 19.0711L16.2426 17.6569L10.5857 12L16.2425 6.34317Z"
></path>
</svg>
</span>
Prev
</button>
) : (
<span></span>
<button className="button-stepper"></button>
)}
<p>{step.toString()}/3</p>
{onNextClick !== undefined ? (
<button className="button-stepper" disabled={!onNextClick} onClick={onNextClick || undefined}>
<button
className="button-stepper"
disabled={!onNextClick}
onClick={onNextClick || undefined}
type="button"
>
Next
<span className="stepper-icon right-pad">
<svg viewBox="0 0 24 24" focusable="false">
<path
fill="currentColor"
d="M10.5859 6.34317L12.0001 4.92896L19.0712 12L12.0001 19.0711L10.5859 17.6569L16.2428 12L10.5859 6.34317Z"
></path>
</svg>
</span>
</button>
) : (
<span></span>
<span className="button-stepper" />
)}
</div>
)

View File

@@ -0,0 +1,37 @@
"use client"
import React, { createContext, ReactNode, useContext, useState } from "react"
export type LogContextType = {
log: string
setLog: (logs: string) => void
}
const LogContext = createContext<LogContextType | null>(null)
interface ProviderProps {
children: ReactNode
}
export const LogContextProvider: React.FC<ProviderProps> = ({ children }) => {
const [log, setLog] = useState<string>("")
return (
<LogContext.Provider
value={{
log,
setLog
}}
>
{children}
</LogContext.Provider>
)
}
export const useLogContext = () => {
const context = useContext(LogContext)
if (context === null) {
throw new Error("LogContext must be used within a LogContextProvider")
}
return context
}

View File

@@ -1,11 +0,0 @@
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

@@ -1,19 +0,0 @@
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,88 @@
"use client"
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from "react"
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
import { decodeBytes32String, toBeHex } from "ethers"
export type SemaphoreContextType = {
_users: string[]
_feedback: string[]
refreshUsers: () => Promise<void>
addUser: (user: string) => void
refreshFeedback: () => Promise<void>
addFeedback: (feedback: string) => void
}
const SemaphoreContext = createContext<SemaphoreContextType | null>(null)
interface ProviderProps {
children: ReactNode
}
const ethereumNetwork =
process.env.NEXT_PUBLIC_DEFAULT_NETWORK === "localhost"
? "http://127.0.0.1:8545"
: process.env.NEXT_PUBLIC_DEFAULT_NETWORK
export const SemaphoreContextProvider: React.FC<ProviderProps> = ({ children }) => {
const [_users, setUsers] = useState<any[]>([])
const [_feedback, setFeedback] = useState<string[]>([])
const refreshUsers = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreSubgraph(ethereumNetwork)
const members = await semaphore.getGroupMembers(process.env.NEXT_PUBLIC_GROUP_ID as string)
setUsers(members.map((member) => member.toString()))
}, [])
const addUser = useCallback(
(user: any) => {
setUsers([..._users, user])
},
[_users]
)
const refreshFeedback = useCallback(async (): Promise<void> => {
const semaphore = new SemaphoreSubgraph(ethereumNetwork)
const proofs = await semaphore.getGroupValidatedProofs(process.env.NEXT_PUBLIC_GROUP_ID as string)
setFeedback(proofs.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32))))
}, [])
const addFeedback = useCallback(
(feedback: string) => {
setFeedback([..._feedback, feedback])
},
[_feedback]
)
useEffect(() => {
refreshUsers()
refreshFeedback()
}, [refreshFeedback, refreshUsers])
return (
<SemaphoreContext.Provider
value={{
_users,
_feedback,
refreshUsers,
addUser,
refreshFeedback,
addFeedback
}}
>
{children}
</SemaphoreContext.Provider>
)
}
export const useSemaphoreContext = () => {
const context = useContext(SemaphoreContext)
if (context === null) {
throw new Error("SemaphoreContext must be used within a SemaphoreContextProvider")
}
return context
}

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