diff --git a/.eslintignore b/.eslintignore
index 1d32fe2d..3cf5385d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -24,6 +24,10 @@ dist
build
/docs
+# subgraph
+subgraph.template.yaml
+generated
+
# Docusaurus cache and generated files
.docusaurus
.cache-loader
diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml
index 7c4956b8..f1d3a513 100644
--- a/.github/workflows/production.yml
+++ b/.github/workflows/production.yml
@@ -43,12 +43,48 @@ jobs:
- name: Build libraries
run: yarn build:libraries
+ - name: Build subgraph
+ run: yarn build:subgraph
+
- name: Run Prettier
run: yarn prettier
- name: Run Eslint
run: yarn lint
+ test-subgraph:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16.x
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
+
+ - name: Restore yarn cache
+ uses: actions/cache@v3
+ id: yarn-cache
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Build subgraph
+ run: yarn build:subgraph
+
+ - name: Test subgraph
+ run: yarn test:subgraph
+
test:
runs-on: ubuntu-latest
strategy:
diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml
index 1a94ec74..9339ef1f 100644
--- a/.github/workflows/pull-requests.yml
+++ b/.github/workflows/pull-requests.yml
@@ -41,6 +41,9 @@ jobs:
- name: Build libraries
run: yarn build:libraries
+ - name: Build subgraph
+ run: yarn build:subgraph
+
- name: Run Prettier
run: yarn prettier
@@ -77,5 +80,8 @@ jobs:
- name: Build libraries
run: yarn build:libraries
- - name: Test contracts and libraries
+ - name: Build subgraph
+ run: yarn build:subgraph
+
+ - name: Test contracts, libraries and subgraph
run: yarn test
diff --git a/.prettierignore b/.prettierignore
index 5b70d8a1..1b01a7f1 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -27,6 +27,10 @@ dist
build
/docs
+# subgraph
+subgraph.template.yaml
+generated
+
# github
.github/ISSUE_TEMPLATE
diff --git a/apps/subgraph/.gitignore b/apps/subgraph/.gitignore
new file mode 100644
index 00000000..cc96ad5c
--- /dev/null
+++ b/apps/subgraph/.gitignore
@@ -0,0 +1,5 @@
+# Generate output
+generated
+
+# The Graph
+subgraph.yaml
diff --git a/apps/subgraph/README.md b/apps/subgraph/README.md
new file mode 100644
index 00000000..0fa25361
--- /dev/null
+++ b/apps/subgraph/README.md
@@ -0,0 +1,137 @@
+
+ Semaphore Subgraph
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+| The Graph is an indexing protocol for querying networks like Ethereum and IPFS. Our subgraphs allow you to get data from the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) smart contract. |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+
+## Networks
+
+| Semaphore version | Sepolia | Goerli | Mumbai | Optimism Goerli | Arbitrum Goerli | Arbitrum One |
+| ----------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+| v2.0 | N/A | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) |
+| v2.5 | N/A | [semaphore-protocol/goerli](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli) | N/A | N/A | N/A | N/A |
+| v2.6 | N/A | [semaphore-protocol/goerli-5259d3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-5259d3) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) |
+| v3.0 - v3.1 | N/A | [semaphore-protocol/goerli-89490c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-89490c) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) |
+| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-goerli](https://api.studio.thegraph.com/query/14377/semaphore-goerli/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | [semaphore-optimism-goerli](https://api.studio.thegraph.com/query/14377/semaphore-optimism-goerli/v3.6.1) | [semaphore-arbitrum-goerli](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum-goerli/v3.6.1) | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) |
+
+## 🛠 Install
+
+Clone this repository:
+
+```bash
+git clone https://github.com/semaphore-protocol/semaphore.git
+```
+
+and install the dependencies:
+
+```bash
+cd semaphore/apps/subgraph && yarn
+```
+
+## Usage
+
+The subgraph definition consists of a few files:
+
+- `subgraph.template.yaml`: a YAML file containing the subgraph manifest,
+- `schema.graphql`: a GraphQL schema that defines what data is stored for the subgraph, and how to query it via GraphQL,
+- `src/semaphore.ts`: AssemblyScript code that translates from the event data to the entities defined in the schema.
+
+### Code generation
+
+Generate AssemblyScript types for the subgraph (required every time the schema changes):
+
+```bash
+yarn codegen
+```
+
+It also generates a `subgraph.yaml` file for your specific network.
+
+### Testing
+
+After generating the types and `subgraph.yaml` file, test your subgraph:
+
+```bash
+yarn test
+```
+
+### Deployment
+
+#### TheGraph Studio
+
+Set the authorization code that links your account on thegraph.com:
+
+```bash
+yarn auth
+```
+
+Deploy the subgraph to the [TheGraph Studio](https://thegraph.com/studio/):
+
+```bash
+yarn deploy
+```
+
+#### Local
+
+Start services required for TheGraph node by running:
+
+```bash
+docker compose -f docker-compose-graph.yml up
+```
+
+Start a local Hardhat node and deploy the [Semaphore contract](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts):
+
+```bash
+# CWD = /semaphore/packages/contracts
+yarn start
+yarn deploy:semaphore --network localhost
+```
+
+Create the `subgraph.yaml` file for your local network and create/deploy your subgraph:
+
+```bash
+yarn codegen localhost
+yarn create-local
+yarn deploy-local
+```
+
+Once the subgraph is published it will start indexing. You can query the subgraph using the following GraphQL endpoint:
+
+```
+http://127.0.0.1:8000/subgraphs/name/sempahore/graphql
+```
diff --git a/apps/subgraph/abis/Semaphore.json b/apps/subgraph/abis/Semaphore.json
new file mode 100644
index 00000000..5a052415
--- /dev/null
+++ b/apps/subgraph/abis/Semaphore.json
@@ -0,0 +1,626 @@
+{
+ "_format": "hh-sol-artifact-1",
+ "contractName": "Semaphore",
+ "sourceName": "contracts/Semaphore.sol",
+ "abi": [
+ {
+ "inputs": [
+ {
+ "internalType": "contract ISemaphoreVerifier",
+ "name": "_verifier",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__CallerIsNotTheGroupAdmin",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__GroupAlreadyExists",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__GroupDoesNotExist",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__MerkleTreeDepthIsNotSupported",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__MerkleTreeRootIsExpired",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__MerkleTreeRootIsNotPartOfTheGroup",
+ "type": "error"
+ },
+ {
+ "inputs": [],
+ "name": "Semaphore__YouAreUsingTheSameNillifierTwice",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "oldAdmin",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newAdmin",
+ "type": "address"
+ }
+ ],
+ "name": "GroupAdminUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "merkleTreeDepth",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "zeroValue",
+ "type": "uint256"
+ }
+ ],
+ "name": "GroupCreated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "oldMerkleTreeDuration",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newMerkleTreeDuration",
+ "type": "uint256"
+ }
+ ],
+ "name": "GroupMerkleTreeDurationUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "identityCommitment",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "merkleTreeRoot",
+ "type": "uint256"
+ }
+ ],
+ "name": "MemberAdded",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "identityCommitment",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "merkleTreeRoot",
+ "type": "uint256"
+ }
+ ],
+ "name": "MemberRemoved",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "index",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "identityCommitment",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "newIdentityCommitment",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "merkleTreeRoot",
+ "type": "uint256"
+ }
+ ],
+ "name": "MemberUpdated",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "merkleTreeRoot",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "nullifierHash",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "externalNullifier",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "signal",
+ "type": "uint256"
+ }
+ ],
+ "name": "ProofVerified",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "identityCommitment",
+ "type": "uint256"
+ }
+ ],
+ "name": "addMember",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "identityCommitments",
+ "type": "uint256[]"
+ }
+ ],
+ "name": "addMembers",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "merkleTreeDepth",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "admin",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "merkleTreeDuration",
+ "type": "uint256"
+ }
+ ],
+ "name": "createGroup",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "merkleTreeDepth",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "admin",
+ "type": "address"
+ }
+ ],
+ "name": "createGroup",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ }
+ ],
+ "name": "getMerkleTreeDepth",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ }
+ ],
+ "name": "getMerkleTreeRoot",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ }
+ ],
+ "name": "getNumberOfMerkleTreeLeaves",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "groups",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "admin",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "merkleTreeDuration",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "identityCommitment",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "proofSiblings",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "uint8[]",
+ "name": "proofPathIndices",
+ "type": "uint8[]"
+ }
+ ],
+ "name": "removeMember",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "newAdmin",
+ "type": "address"
+ }
+ ],
+ "name": "updateGroupAdmin",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newMerkleTreeDuration",
+ "type": "uint256"
+ }
+ ],
+ "name": "updateGroupMerkleTreeDuration",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "identityCommitment",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "newIdentityCommitment",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256[]",
+ "name": "proofSiblings",
+ "type": "uint256[]"
+ },
+ {
+ "internalType": "uint8[]",
+ "name": "proofPathIndices",
+ "type": "uint8[]"
+ }
+ ],
+ "name": "updateMember",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "verifier",
+ "outputs": [
+ {
+ "internalType": "contract ISemaphoreVerifier",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "groupId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "merkleTreeRoot",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "signal",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "nullifierHash",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256",
+ "name": "externalNullifier",
+ "type": "uint256"
+ },
+ {
+ "internalType": "uint256[8]",
+ "name": "proof",
+ "type": "uint256[8]"
+ }
+ ],
+ "name": "verifyProof",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+ ],
+ "bytecode": "0x60806040523480156200001157600080fd5b506040516200214a3803806200214a833981810160405281019062000037919062000096565b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505062000124565b60008151905062000090816200010a565b92915050565b600060208284031215620000a957600080fd5b6000620000b9848285016200007f565b91505092915050565b6000620000cf82620000ea565b9050919050565b6000620000e382620000c2565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6200011581620000d6565b81146200012157600080fd5b50565b61201680620001346000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636389e1071161008c5780639c112141116100665780639c11214114610246578063dabc4d5114610262578063ec45622a14610292578063fcf0b6ec146102ae576100ea565b80636389e107146101c957806365e54f83146101f957806396324bd414610215576100ea565b80633bc778e3116100c85780633bc778e31461014557806343989f8514610161578063568ee8261461017d578063638480be14610199576100ea565b806304245371146100ef5780631783efc31461010b5780632b7ac3f314610127575b600080fd5b61010960048036038101906101049190611655565b6102ca565b005b610125600480360381019061012091906116ad565b610410565b005b61012f6104fc565b60405161013c9190611b24565b60405180910390f35b61015f600480360381019061015a91906118e6565b610522565b005b61017b6004803603810190610176919061179b565b6107cf565b005b61019760048036038101906101929190611619565b6108c3565b005b6101b360048036038101906101ae91906115f0565b610a22565b6040516101c09190611c5b565b60405180910390f35b6101e360048036038101906101de91906115f0565b610a41565b6040516101f09190611c5b565b60405180910390f35b610213600480360381019061020e9190611738565b610a60565b005b61022f600480360381019061022a91906115f0565b610b84565b60405161023d929190611afb565b60405180910390f35b610260600480360381019061025b91906116e9565b610bc8565b005b61027c600480360381019061027791906115f0565b610ced565b6040516102899190611c5b565b60405180910390f35b6102ac60048036038101906102a79190611836565b610d0c565b005b6102c860048036038101906102c391906116ad565b610e02565b005b826102d3610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461036d576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838390508110156103cf576103c4858585848181106103b8577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020135610f23565b806001019050610370565b5060006103db85610ced565b905042600260008781526020019081526020016000206002016000838152602001908152602001600020819055505050505050565b81610419610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146104b3576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104bd8383610f23565b60006104c884610ced565b9050426002600086815260200190815260200160002060020160008381526020019081526020016000208190555050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600061052d87610a41565b9050600081141561056a576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061057588610ced565b9050808714610648576000600260008a815260200190815260200160002060020160008981526020019081526020016000205490506000600260008b81526020019081526020016000206001015490506000821415610600576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808261060c9190611dce565b421115610645576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b60026000898152602001908152602001600020600301600086815260200190815260200160002060009054906101000a900460ff16156106b4576040517f948d067400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639aca8f2a8887898888886040518763ffffffff1660e01b815260040161071996959493929190611d1b565b60006040518083038186803b15801561073157600080fd5b505afa158015610745573d6000803e3d6000fd5b505050506001600260008a8152602001908152602001600020600301600087815260200190815260200160002060006101000a81548160ff0219169083151502179055508387897f48950129900df26c2140187532df49c8af343c3daf74f1e99e562e1b2be07adc888a6040516107bd929190611c76565b60405180910390a45050505050505050565b856107d8610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610872576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61088087878787878761104a565b600061088b88610ced565b905042600260008a81526020019081526020016000206002016000838152602001908152602001600020819055505050505050505050565b816108cc610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610966576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816002600085815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff166109da610f1b565b73ffffffffffffffffffffffffffffffffffffffff16847f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a4505050565b6000806000838152602001908152602001600020600201549050919050565b6000806000838152602001908152602001600020600001549050919050565b826010811080610a705750602081115b15610aa7576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ab18585611172565b826002600087815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000878152602001908152602001600020600101819055508273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16867f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050505050565b60026020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010154905082565b816010811080610bd85750602081115b15610c0f576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c198484611172565b816002600086815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610e1060026000868152602001908152602001600020600101819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b6000806000838152602001908152602001600020600101549050919050565b86610d15610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610daf576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dbe888888888888886112a3565b6000610dc989610ced565b905042600260008b8152602001908152602001600020600201600083815260200190815260200160002081905550505050505050505050565b81610e0b610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610ea5576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060026000858152602001908152602001600020600101549050826002600086815260200190815260200160002060010181905550837f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58285604051610f0d929190611c76565b60405180910390a250505050565b600033905090565b6000610f2e83610a41565b1415610f66576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008083815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63168703fa9091836040518363ffffffff1660e01b8152600401610fb2929190611b3f565b60006040518083038186803b158015610fca57600080fd5b505af4158015610fde573d6000803e3d6000fd5b505050506000610fed83610ced565b905060006001610ffc85610a22565b6110069190611e7e565b9050837f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde982858560405161103c93929190611c9f565b60405180910390a250505050565b600061105587610a41565b141561108d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008087815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__630629596f909187878787876040518763ffffffff1660e01b81526004016110e196959493929190611b68565b60006040518083038186803b1580156110f957600080fd5b505af415801561110d573d6000803e3d6000fd5b50505050600061111c87610ced565b9050600061112a84846113d0565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87182898560405161116093929190611c9f565b60405180910390a25050505050505050565b600061117d83610a41565b146111b4576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006008836040516020016111c99190611ae0565b6040516020818303038152906040528051906020012060001c901c905060008084815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__631095fbb4909184846040518463ffffffff1660e01b815260040161123493929190611bbf565b60006040518083038186803b15801561124c57600080fd5b505af4158015611260573d6000803e3d6000fd5b50505050827f0d000126c26c1bbe400fd2332187f75d58b69306f9ec47b408686189d3a008338383604051611296929190611c76565b60405180910390a2505050565b60006112ae88610a41565b14156112e6576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008088815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63a547882790918888888888886040518863ffffffff1660e01b815260040161133c9796959493929190611bf6565b60006040518083038186803b15801561135457600080fd5b505af4158015611368573d6000803e3d6000fd5b50505050600061137788610ced565b9050600061138584846113d0565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1828a8a866040516113bd9493929190611cd6565b60405180910390a2505050505050505050565b6000806000905060008484905090505b60008160ff1611156114f457600082118061145b5750600085856001846114079190611eb2565b60ff16818110611440577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114559190611970565b60ff1614155b156114e85760028261146d9190611e24565b9150600185856001846114809190611eb2565b60ff168181106114b9577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114ce9190611970565b60ff1614156114e7576001826114e49190611dce565b91505b5b806001900390506113e0565b508091505092915050565b60008135905061150e81611f9b565b92915050565b60008190508260206008028201111561152c57600080fd5b92915050565b60008083601f84011261154457600080fd5b8235905067ffffffffffffffff81111561155d57600080fd5b60208301915083602082028301111561157557600080fd5b9250929050565b60008083601f84011261158e57600080fd5b8235905067ffffffffffffffff8111156115a757600080fd5b6020830191508360208202830111156115bf57600080fd5b9250929050565b6000813590506115d581611fb2565b92915050565b6000813590506115ea81611fc9565b92915050565b60006020828403121561160257600080fd5b6000611610848285016115c6565b91505092915050565b6000806040838503121561162c57600080fd5b600061163a858286016115c6565b925050602061164b858286016114ff565b9150509250929050565b60008060006040848603121561166a57600080fd5b6000611678868287016115c6565b935050602084013567ffffffffffffffff81111561169557600080fd5b6116a186828701611532565b92509250509250925092565b600080604083850312156116c057600080fd5b60006116ce858286016115c6565b92505060206116df858286016115c6565b9150509250929050565b6000806000606084860312156116fe57600080fd5b600061170c868287016115c6565b935050602061171d868287016115c6565b925050604061172e868287016114ff565b9150509250925092565b6000806000806080858703121561174e57600080fd5b600061175c878288016115c6565b945050602061176d878288016115c6565b935050604061177e878288016114ff565b925050606061178f878288016115c6565b91505092959194509250565b600080600080600080608087890312156117b457600080fd5b60006117c289828a016115c6565b96505060206117d389828a016115c6565b955050604087013567ffffffffffffffff8111156117f057600080fd5b6117fc89828a01611532565b9450945050606087013567ffffffffffffffff81111561181b57600080fd5b61182789828a0161157c565b92509250509295509295509295565b600080600080600080600060a0888a03121561185157600080fd5b600061185f8a828b016115c6565b97505060206118708a828b016115c6565b96505060406118818a828b016115c6565b955050606088013567ffffffffffffffff81111561189e57600080fd5b6118aa8a828b01611532565b9450945050608088013567ffffffffffffffff8111156118c957600080fd5b6118d58a828b0161157c565b925092505092959891949750929550565b6000806000806000806101a0878903121561190057600080fd5b600061190e89828a016115c6565b965050602061191f89828a016115c6565b955050604061193089828a016115c6565b945050606061194189828a016115c6565b935050608061195289828a016115c6565b92505060a061196389828a01611514565b9150509295509295509295565b60006020828403121561198257600080fd5b6000611990848285016115db565b91505092915050565b60006119a58383611ad1565b60208301905092915050565b6119ba81611ee6565b82525050565b6119cd6101008383611f53565b5050565b60006119dd8385611d95565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115611a0c57600080fd5b602083029250611a1d838584611f53565b82840190509392505050565b6000611a358385611da6565b9350611a4082611d7e565b8060005b85811015611a7957611a568284611db7565b611a608882611999565b9750611a6b83611d88565b925050600181019050611a44565b5085925050509392505050565b611a8f81611f2f565b82525050565b8082525050565b611aa581611f18565b82525050565b611ab481611f18565b82525050565b611acb611ac682611f18565b611f62565b82525050565b611ada81611f22565b82525050565b6000611aec8284611aba565b60208201915081905092915050565b6000604082019050611b1060008301856119b1565b611b1d6020830184611a9c565b9392505050565b6000602082019050611b396000830184611a86565b92915050565b6000604082019050611b546000830185611a95565b611b616020830184611aab565b9392505050565b6000608082019050611b7d6000830189611a95565b611b8a6020830188611aab565b8181036040830152611b9d8186886119d1565b90508181036060830152611bb2818486611a29565b9050979650505050505050565b6000606082019050611bd46000830186611a95565b611be16020830185611aab565b611bee6040830184611aab565b949350505050565b600060a082019050611c0b600083018a611a95565b611c186020830189611aab565b611c256040830188611aab565b8181036060830152611c388186886119d1565b90508181036080830152611c4d818486611a29565b905098975050505050505050565b6000602082019050611c706000830184611a9c565b92915050565b6000604082019050611c8b6000830185611a9c565b611c986020830184611a9c565b9392505050565b6000606082019050611cb46000830186611a9c565b611cc16020830185611a9c565b611cce6040830184611a9c565b949350505050565b6000608082019050611ceb6000830187611a9c565b611cf86020830186611a9c565b611d056040830185611a9c565b611d126060830184611a9c565b95945050505050565b60006101a082019050611d316000830189611a9c565b611d3e6020830188611a9c565b611d4b6040830187611a9c565b611d586060830186611a9c565b611d6560808301856119c0565b611d73610180830184611a9c565b979650505050505050565b6000819050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b6000611dc660208401846115db565b905092915050565b6000611dd982611f18565b9150611de483611f18565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611e1957611e18611f6c565b5b828201905092915050565b6000611e2f82611f18565b9150611e3a83611f18565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7357611e72611f6c565b5b828202905092915050565b6000611e8982611f18565b9150611e9483611f18565b925082821015611ea757611ea6611f6c565b5b828203905092915050565b6000611ebd82611f22565b9150611ec883611f22565b925082821015611edb57611eda611f6c565b5b828203905092915050565b6000611ef182611ef8565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b6000611f3a82611f41565b9050919050565b6000611f4c82611ef8565b9050919050565b82818337600083830152505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611fa481611ee6565b8114611faf57600080fd5b50565b611fbb81611f18565b8114611fc657600080fd5b50565b611fd281611f22565b8114611fdd57600080fd5b5056fea2646970667358221220a6777edc645a16d8f81beacbb5ec133f19452c578127e38e7f40d5540508d08464736f6c63430008040033",
+ "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636389e1071161008c5780639c112141116100665780639c11214114610246578063dabc4d5114610262578063ec45622a14610292578063fcf0b6ec146102ae576100ea565b80636389e107146101c957806365e54f83146101f957806396324bd414610215576100ea565b80633bc778e3116100c85780633bc778e31461014557806343989f8514610161578063568ee8261461017d578063638480be14610199576100ea565b806304245371146100ef5780631783efc31461010b5780632b7ac3f314610127575b600080fd5b61010960048036038101906101049190611655565b6102ca565b005b610125600480360381019061012091906116ad565b610410565b005b61012f6104fc565b60405161013c9190611b24565b60405180910390f35b61015f600480360381019061015a91906118e6565b610522565b005b61017b6004803603810190610176919061179b565b6107cf565b005b61019760048036038101906101929190611619565b6108c3565b005b6101b360048036038101906101ae91906115f0565b610a22565b6040516101c09190611c5b565b60405180910390f35b6101e360048036038101906101de91906115f0565b610a41565b6040516101f09190611c5b565b60405180910390f35b610213600480360381019061020e9190611738565b610a60565b005b61022f600480360381019061022a91906115f0565b610b84565b60405161023d929190611afb565b60405180910390f35b610260600480360381019061025b91906116e9565b610bc8565b005b61027c600480360381019061027791906115f0565b610ced565b6040516102899190611c5b565b60405180910390f35b6102ac60048036038101906102a79190611836565b610d0c565b005b6102c860048036038101906102c391906116ad565b610e02565b005b826102d3610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461036d576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838390508110156103cf576103c4858585848181106103b8577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020135610f23565b806001019050610370565b5060006103db85610ced565b905042600260008781526020019081526020016000206002016000838152602001908152602001600020819055505050505050565b81610419610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146104b3576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104bd8383610f23565b60006104c884610ced565b9050426002600086815260200190815260200160002060020160008381526020019081526020016000208190555050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600061052d87610a41565b9050600081141561056a576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061057588610ced565b9050808714610648576000600260008a815260200190815260200160002060020160008981526020019081526020016000205490506000600260008b81526020019081526020016000206001015490506000821415610600576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808261060c9190611dce565b421115610645576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b60026000898152602001908152602001600020600301600086815260200190815260200160002060009054906101000a900460ff16156106b4576040517f948d067400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639aca8f2a8887898888886040518763ffffffff1660e01b815260040161071996959493929190611d1b565b60006040518083038186803b15801561073157600080fd5b505afa158015610745573d6000803e3d6000fd5b505050506001600260008a8152602001908152602001600020600301600087815260200190815260200160002060006101000a81548160ff0219169083151502179055508387897f48950129900df26c2140187532df49c8af343c3daf74f1e99e562e1b2be07adc888a6040516107bd929190611c76565b60405180910390a45050505050505050565b856107d8610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610872576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61088087878787878761104a565b600061088b88610ced565b905042600260008a81526020019081526020016000206002016000838152602001908152602001600020819055505050505050505050565b816108cc610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610966576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816002600085815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff166109da610f1b565b73ffffffffffffffffffffffffffffffffffffffff16847f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a4505050565b6000806000838152602001908152602001600020600201549050919050565b6000806000838152602001908152602001600020600001549050919050565b826010811080610a705750602081115b15610aa7576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ab18585611172565b826002600087815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000878152602001908152602001600020600101819055508273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16867f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050505050565b60026020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010154905082565b816010811080610bd85750602081115b15610c0f576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c198484611172565b816002600086815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610e1060026000868152602001908152602001600020600101819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b6000806000838152602001908152602001600020600101549050919050565b86610d15610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610daf576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dbe888888888888886112a3565b6000610dc989610ced565b905042600260008b8152602001908152602001600020600201600083815260200190815260200160002081905550505050505050505050565b81610e0b610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610ea5576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060026000858152602001908152602001600020600101549050826002600086815260200190815260200160002060010181905550837f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58285604051610f0d929190611c76565b60405180910390a250505050565b600033905090565b6000610f2e83610a41565b1415610f66576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008083815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63168703fa9091836040518363ffffffff1660e01b8152600401610fb2929190611b3f565b60006040518083038186803b158015610fca57600080fd5b505af4158015610fde573d6000803e3d6000fd5b505050506000610fed83610ced565b905060006001610ffc85610a22565b6110069190611e7e565b9050837f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde982858560405161103c93929190611c9f565b60405180910390a250505050565b600061105587610a41565b141561108d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008087815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__630629596f909187878787876040518763ffffffff1660e01b81526004016110e196959493929190611b68565b60006040518083038186803b1580156110f957600080fd5b505af415801561110d573d6000803e3d6000fd5b50505050600061111c87610ced565b9050600061112a84846113d0565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87182898560405161116093929190611c9f565b60405180910390a25050505050505050565b600061117d83610a41565b146111b4576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006008836040516020016111c99190611ae0565b6040516020818303038152906040528051906020012060001c901c905060008084815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__631095fbb4909184846040518463ffffffff1660e01b815260040161123493929190611bbf565b60006040518083038186803b15801561124c57600080fd5b505af4158015611260573d6000803e3d6000fd5b50505050827f0d000126c26c1bbe400fd2332187f75d58b69306f9ec47b408686189d3a008338383604051611296929190611c76565b60405180910390a2505050565b60006112ae88610a41565b14156112e6576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008088815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63a547882790918888888888886040518863ffffffff1660e01b815260040161133c9796959493929190611bf6565b60006040518083038186803b15801561135457600080fd5b505af4158015611368573d6000803e3d6000fd5b50505050600061137788610ced565b9050600061138584846113d0565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1828a8a866040516113bd9493929190611cd6565b60405180910390a2505050505050505050565b6000806000905060008484905090505b60008160ff1611156114f457600082118061145b5750600085856001846114079190611eb2565b60ff16818110611440577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114559190611970565b60ff1614155b156114e85760028261146d9190611e24565b9150600185856001846114809190611eb2565b60ff168181106114b9577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114ce9190611970565b60ff1614156114e7576001826114e49190611dce565b91505b5b806001900390506113e0565b508091505092915050565b60008135905061150e81611f9b565b92915050565b60008190508260206008028201111561152c57600080fd5b92915050565b60008083601f84011261154457600080fd5b8235905067ffffffffffffffff81111561155d57600080fd5b60208301915083602082028301111561157557600080fd5b9250929050565b60008083601f84011261158e57600080fd5b8235905067ffffffffffffffff8111156115a757600080fd5b6020830191508360208202830111156115bf57600080fd5b9250929050565b6000813590506115d581611fb2565b92915050565b6000813590506115ea81611fc9565b92915050565b60006020828403121561160257600080fd5b6000611610848285016115c6565b91505092915050565b6000806040838503121561162c57600080fd5b600061163a858286016115c6565b925050602061164b858286016114ff565b9150509250929050565b60008060006040848603121561166a57600080fd5b6000611678868287016115c6565b935050602084013567ffffffffffffffff81111561169557600080fd5b6116a186828701611532565b92509250509250925092565b600080604083850312156116c057600080fd5b60006116ce858286016115c6565b92505060206116df858286016115c6565b9150509250929050565b6000806000606084860312156116fe57600080fd5b600061170c868287016115c6565b935050602061171d868287016115c6565b925050604061172e868287016114ff565b9150509250925092565b6000806000806080858703121561174e57600080fd5b600061175c878288016115c6565b945050602061176d878288016115c6565b935050604061177e878288016114ff565b925050606061178f878288016115c6565b91505092959194509250565b600080600080600080608087890312156117b457600080fd5b60006117c289828a016115c6565b96505060206117d389828a016115c6565b955050604087013567ffffffffffffffff8111156117f057600080fd5b6117fc89828a01611532565b9450945050606087013567ffffffffffffffff81111561181b57600080fd5b61182789828a0161157c565b92509250509295509295509295565b600080600080600080600060a0888a03121561185157600080fd5b600061185f8a828b016115c6565b97505060206118708a828b016115c6565b96505060406118818a828b016115c6565b955050606088013567ffffffffffffffff81111561189e57600080fd5b6118aa8a828b01611532565b9450945050608088013567ffffffffffffffff8111156118c957600080fd5b6118d58a828b0161157c565b925092505092959891949750929550565b6000806000806000806101a0878903121561190057600080fd5b600061190e89828a016115c6565b965050602061191f89828a016115c6565b955050604061193089828a016115c6565b945050606061194189828a016115c6565b935050608061195289828a016115c6565b92505060a061196389828a01611514565b9150509295509295509295565b60006020828403121561198257600080fd5b6000611990848285016115db565b91505092915050565b60006119a58383611ad1565b60208301905092915050565b6119ba81611ee6565b82525050565b6119cd6101008383611f53565b5050565b60006119dd8385611d95565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115611a0c57600080fd5b602083029250611a1d838584611f53565b82840190509392505050565b6000611a358385611da6565b9350611a4082611d7e565b8060005b85811015611a7957611a568284611db7565b611a608882611999565b9750611a6b83611d88565b925050600181019050611a44565b5085925050509392505050565b611a8f81611f2f565b82525050565b8082525050565b611aa581611f18565b82525050565b611ab481611f18565b82525050565b611acb611ac682611f18565b611f62565b82525050565b611ada81611f22565b82525050565b6000611aec8284611aba565b60208201915081905092915050565b6000604082019050611b1060008301856119b1565b611b1d6020830184611a9c565b9392505050565b6000602082019050611b396000830184611a86565b92915050565b6000604082019050611b546000830185611a95565b611b616020830184611aab565b9392505050565b6000608082019050611b7d6000830189611a95565b611b8a6020830188611aab565b8181036040830152611b9d8186886119d1565b90508181036060830152611bb2818486611a29565b9050979650505050505050565b6000606082019050611bd46000830186611a95565b611be16020830185611aab565b611bee6040830184611aab565b949350505050565b600060a082019050611c0b600083018a611a95565b611c186020830189611aab565b611c256040830188611aab565b8181036060830152611c388186886119d1565b90508181036080830152611c4d818486611a29565b905098975050505050505050565b6000602082019050611c706000830184611a9c565b92915050565b6000604082019050611c8b6000830185611a9c565b611c986020830184611a9c565b9392505050565b6000606082019050611cb46000830186611a9c565b611cc16020830185611a9c565b611cce6040830184611a9c565b949350505050565b6000608082019050611ceb6000830187611a9c565b611cf86020830186611a9c565b611d056040830185611a9c565b611d126060830184611a9c565b95945050505050565b60006101a082019050611d316000830189611a9c565b611d3e6020830188611a9c565b611d4b6040830187611a9c565b611d586060830186611a9c565b611d6560808301856119c0565b611d73610180830184611a9c565b979650505050505050565b6000819050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b6000611dc660208401846115db565b905092915050565b6000611dd982611f18565b9150611de483611f18565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611e1957611e18611f6c565b5b828201905092915050565b6000611e2f82611f18565b9150611e3a83611f18565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7357611e72611f6c565b5b828202905092915050565b6000611e8982611f18565b9150611e9483611f18565b925082821015611ea757611ea6611f6c565b5b828203905092915050565b6000611ebd82611f22565b9150611ec883611f22565b925082821015611edb57611eda611f6c565b5b828203905092915050565b6000611ef182611ef8565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b6000611f3a82611f41565b9050919050565b6000611f4c82611ef8565b9050919050565b82818337600083830152505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611fa481611ee6565b8114611faf57600080fd5b50565b611fbb81611f18565b8114611fc657600080fd5b50565b611fd281611f22565b8114611fdd57600080fd5b5056fea2646970667358221220a6777edc645a16d8f81beacbb5ec133f19452c578127e38e7f40d5540508d08464736f6c63430008040033",
+ "linkReferences": {
+ "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol": {
+ "IncrementalBinaryTree": [
+ {
+ "length": 20,
+ "start": 4270
+ },
+ {
+ "length": 20,
+ "start": 4565
+ },
+ {
+ "length": 20,
+ "start": 4910
+ },
+ {
+ "length": 20,
+ "start": 5166
+ }
+ ]
+ }
+ },
+ "deployedLinkReferences": {
+ "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol": {
+ "IncrementalBinaryTree": [
+ {
+ "length": 20,
+ "start": 3962
+ },
+ {
+ "length": 20,
+ "start": 4257
+ },
+ {
+ "length": 20,
+ "start": 4602
+ },
+ {
+ "length": 20,
+ "start": 4858
+ }
+ ]
+ }
+ }
+}
diff --git a/apps/subgraph/docker-compose-graph.yml b/apps/subgraph/docker-compose-graph.yml
new file mode 100644
index 00000000..332efbc5
--- /dev/null
+++ b/apps/subgraph/docker-compose-graph.yml
@@ -0,0 +1,60 @@
+version: "3.9"
+
+services:
+ graph-node:
+ image: graphprotocol/graph-node
+ ports:
+ - "8000:8000"
+ - "8001:8001"
+ - "8020:8020"
+ - "8030:8030"
+ - "8040:8040"
+ depends_on:
+ - ipfs
+ - postgres
+ environment:
+ postgres_host: postgres
+ postgres_user: graph-node
+ postgres_pass: let-me-in
+ postgres_db: graph-node
+ postgres_port: 5432
+ ipfs: "ipfs:5001"
+ ethereum: "localhost:http://host.docker.internal:8545" # Should use the `localhost` as network name in subgraph.yml
+ # ethereum: 'goerli:https://goerli.infura.io/v3/YOUR-API-KEY'
+ GRAPH_LOG: info
+ GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1
+ networks:
+ - the-graph
+
+ ipfs:
+ image: ipfs/go-ipfs:v0.4.23
+ ports:
+ - "5001:5001"
+ volumes:
+ - graph-ipfs:/data/ipfs
+ networks:
+ - the-graph
+
+ postgres:
+ image: postgres
+ ports:
+ - "5433:5432"
+ command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
+ environment:
+ POSTGRES_USER: graph-node
+ POSTGRES_PASSWORD: let-me-in
+ POSTGRES_DB: graph-node
+ POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
+ networks:
+ - the-graph
+ volumes:
+ - graph-postgres:/var/lib/postgresql/data
+
+networks:
+ the-graph:
+ internal: false
+ driver: bridge
+
+volumes:
+ graph-postgres:
+ graph-ipfs:
diff --git a/apps/subgraph/matchstick.yaml b/apps/subgraph/matchstick.yaml
new file mode 100644
index 00000000..8851578c
--- /dev/null
+++ b/apps/subgraph/matchstick.yaml
@@ -0,0 +1 @@
+libsFolder: ../../node_modules
diff --git a/apps/subgraph/networks.json b/apps/subgraph/networks.json
new file mode 100644
index 00000000..2066652d
--- /dev/null
+++ b/apps/subgraph/networks.json
@@ -0,0 +1,44 @@
+{
+ "sepolia": {
+ "Semaphore": {
+ "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
+ "startBlock": 3231111
+ }
+ },
+ "goerli": {
+ "Semaphore": {
+ "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
+ "startBlock": 8777695
+ }
+ },
+ "mumbai": {
+ "Semaphore": {
+ "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
+ "startBlock": 33995010
+ }
+ },
+ "optimism-goerli": {
+ "Semaphore": {
+ "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
+ "startBlock": 7632846
+ }
+ },
+ "arbitrum-goerli": {
+ "Semaphore": {
+ "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
+ "startBlock": 15174410
+ }
+ },
+ "arbitrum-one": {
+ "Semaphore": {
+ "address": "0xc60E0Ee1a2770d5F619858C641f14FC4a6401520",
+ "startBlock": 77278430
+ }
+ },
+ "localhost": {
+ "Semaphore": {
+ "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
+ "startBlock": 0
+ }
+ }
+}
diff --git a/apps/subgraph/package.json b/apps/subgraph/package.json
new file mode 100644
index 00000000..1cff22ed
--- /dev/null
+++ b/apps/subgraph/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "semaphore-subgraph",
+ "type": "module",
+ "description": "Semaphore subgraph definition (The Graph).",
+ "license": "MIT",
+ "private": true,
+ "scripts": {
+ "codegen": "node scripts/generateSubgraph.js ${0} && graph codegen",
+ "build": "graph build",
+ "auth": "graph auth --studio",
+ "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ ${0}",
+ "create-local": "graph create --node http://localhost:8020/ semaphore",
+ "remove-local": "graph remove --node http://localhost:8020/ semaphore",
+ "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 semaphore",
+ "test": "graph test Semaphore -v 0.5.0"
+ },
+ "dependencies": {
+ "@graphprotocol/graph-cli": "0.56.0",
+ "@graphprotocol/graph-ts": "^0.31.0"
+ },
+ "devDependencies": {
+ "@types/mustache": "^4.2.2",
+ "matchstick-as": "0.5.0",
+ "mustache": "^4.2.0"
+ }
+}
diff --git a/apps/subgraph/schema.graphql b/apps/subgraph/schema.graphql
new file mode 100644
index 00000000..f8d238b2
--- /dev/null
+++ b/apps/subgraph/schema.graphql
@@ -0,0 +1,35 @@
+type MerkleTree @entity {
+ id: ID!
+ depth: BigInt!
+ root: BigInt
+ zeroValue: BigInt!
+ numberOfLeaves: Int!
+ group: Group!
+}
+
+type Group @entity {
+ id: ID!
+ merkleTree: MerkleTree!
+ timestamp: BigInt!
+ admin: Bytes
+ members: [Member!] @derivedFrom(field: "group")
+ verifiedProofs: [VerifiedProof!] @derivedFrom(field: "group")
+}
+
+type Member @entity {
+ id: ID!
+ identityCommitment: BigInt!
+ timestamp: BigInt!
+ index: Int!
+ group: Group!
+}
+
+type VerifiedProof @entity {
+ id: ID!
+ signal: BigInt!
+ merkleTreeRoot: BigInt!
+ nullifierHash: BigInt!
+ externalNullifier: BigInt!
+ timestamp: BigInt!
+ group: Group!
+}
diff --git a/apps/subgraph/scripts/generateSubgraph.js b/apps/subgraph/scripts/generateSubgraph.js
new file mode 100644
index 00000000..8b867969
--- /dev/null
+++ b/apps/subgraph/scripts/generateSubgraph.js
@@ -0,0 +1,10 @@
+import Mustache from "mustache"
+import { readFileSync, writeFileSync } from "fs"
+
+const network = process.argv.at(2)
+
+const template = readFileSync("./subgraph.template.yaml", "utf-8")
+const networks = JSON.parse(readFileSync("./networks.json", "utf-8"))
+const subgraph = Mustache.render(template, { network, ...networks[network].Semaphore })
+
+writeFileSync("./subgraph.yaml", subgraph)
diff --git a/apps/subgraph/src/semaphore.ts b/apps/subgraph/src/semaphore.ts
new file mode 100644
index 00000000..1bc56436
--- /dev/null
+++ b/apps/subgraph/src/semaphore.ts
@@ -0,0 +1,191 @@
+import { ByteArray, log } from "@graphprotocol/graph-ts"
+import {
+ GroupAdminUpdated,
+ GroupCreated,
+ MemberAdded,
+ MemberRemoved,
+ MemberUpdated,
+ ProofVerified
+} from "../generated/Semaphore/Semaphore"
+import { Member, Group, VerifiedProof, MerkleTree } from "../generated/schema"
+import { concat, hash } from "./utils"
+
+/**
+ * Creates a new group.
+ * @param event Ethereum event emitted when a group is created.
+ */
+export function createGroup(event: GroupCreated): void {
+ log.debug(`GroupCreated event block: {}`, [event.block.number.toString()])
+
+ const group = new Group(event.params.groupId.toString())
+ const merkleTree = new MerkleTree(event.params.groupId.toString())
+
+ log.info("Creating group '{}'", [group.id])
+
+ merkleTree.depth = event.params.merkleTreeDepth
+ merkleTree.zeroValue = event.params.zeroValue
+ merkleTree.numberOfLeaves = 0
+ merkleTree.group = group.id
+
+ group.timestamp = event.block.timestamp
+ group.merkleTree = merkleTree.id
+
+ merkleTree.save()
+ group.save()
+
+ log.info("Group '{}' has been created", [group.id])
+}
+
+/**
+ * Updates the admin of a group.
+ * @param event Ethereum event emitted when a group admin is updated.
+ */
+export function updateGroupAdmin(event: GroupAdminUpdated): void {
+ log.debug(`GroupAdminUpdated event block: {}`, [event.block.number.toString()])
+
+ const group = Group.load(event.params.groupId.toString())
+
+ if (group) {
+ log.info("Updating admin '{}' in the group '{}'", [event.params.newAdmin.toString(), group.id])
+
+ group.admin = event.params.newAdmin
+
+ group.save()
+
+ log.info("Admin '{}' of the group '{}' has been updated ", [group.admin!.toString(), group.id])
+ }
+}
+
+/**
+ * Adds a member to a group.
+ * @param event Ethereum event emitted when a member is added to a group.
+ */
+export function addMember(event: MemberAdded): void {
+ log.debug(`MemberAdded event block {}`, [event.block.number.toString()])
+
+ const merkleTree = MerkleTree.load(event.params.groupId.toString())
+
+ if (merkleTree) {
+ const memberId = hash(
+ concat(ByteArray.fromBigInt(event.params.index), ByteArray.fromBigInt(event.params.groupId))
+ )
+ const member = new Member(memberId)
+
+ log.info("Adding member '{}' in the onchain group '{}'", [member.id, merkleTree.group])
+
+ member.group = merkleTree.group
+ member.identityCommitment = event.params.identityCommitment
+ member.timestamp = event.block.timestamp
+ member.index = merkleTree.numberOfLeaves
+
+ member.save()
+
+ merkleTree.root = event.params.merkleTreeRoot
+ merkleTree.numberOfLeaves += 1
+
+ merkleTree.save()
+
+ log.info("Member '{}' of the onchain group '{}' has been added", [member.id, merkleTree.id])
+ }
+}
+
+/**
+ * Updates a member in a group.
+ * @param event Ethereum event emitted when a member is removed from a group.
+ */
+export function updateMember(event: MemberUpdated): void {
+ log.debug(`MemberUpdated event block {}`, [event.block.number.toString()])
+
+ const merkleTree = MerkleTree.load(event.params.groupId.toString())
+
+ if (merkleTree) {
+ const memberId = hash(
+ concat(ByteArray.fromBigInt(event.params.index), ByteArray.fromBigInt(event.params.groupId))
+ )
+ const member = Member.load(memberId)
+
+ if (member) {
+ log.info("Updating member '{}' from the onchain group '{}'", [member.id, merkleTree.group])
+
+ member.identityCommitment = event.params.newIdentityCommitment
+
+ member.save()
+
+ merkleTree.root = event.params.merkleTreeRoot
+
+ merkleTree.save()
+
+ log.info("Member '{}' of the onchain group '{}' has been removed", [member.id, merkleTree.group])
+ }
+ }
+}
+
+/**
+ * Removes a member from a group.
+ * @param event Ethereum event emitted when a member is removed from a group.
+ */
+export function removeMember(event: MemberRemoved): void {
+ log.debug(`MemberRemoved event block {}`, [event.block.number.toString()])
+
+ const merkleTree = MerkleTree.load(event.params.groupId.toString())
+
+ if (merkleTree) {
+ const memberId = hash(
+ concat(ByteArray.fromBigInt(event.params.index), ByteArray.fromBigInt(event.params.groupId))
+ )
+ const member = Member.load(memberId)
+
+ if (member) {
+ log.info("Removing member '{}' from the onchain group '{}'", [member.id, merkleTree.group])
+
+ member.identityCommitment = merkleTree.zeroValue
+
+ member.save()
+
+ merkleTree.root = event.params.merkleTreeRoot
+
+ merkleTree.save()
+
+ log.info("Member '{}' of the onchain group '{}' has been removed", [member.id, merkleTree.group])
+ }
+ }
+}
+
+/**
+ * Adds a verified proof in a group.
+ * @param event Ethereum event emitted when a proof has been verified.
+ */
+export function addVerifiedProof(event: ProofVerified): void {
+ log.debug(`ProofVerified event block {}`, [event.block.number.toString()])
+
+ const group = Group.load(event.params.groupId.toString())
+
+ if (group) {
+ const verifiedProofId = hash(
+ concat(ByteArray.fromBigInt(event.params.nullifierHash), ByteArray.fromBigInt(event.params.groupId))
+ )
+
+ const verifiedProof = new VerifiedProof(verifiedProofId)
+
+ log.info("Adding verified proof with signal '{}' in the onchain group '{}'", [
+ event.params.signal.toHexString(),
+ group.id
+ ])
+
+ verifiedProof.group = group.id
+ verifiedProof.signal = event.params.signal
+ verifiedProof.merkleTreeRoot = event.params.merkleTreeRoot
+ verifiedProof.externalNullifier = event.params.externalNullifier
+ verifiedProof.nullifierHash = event.params.nullifierHash
+ verifiedProof.timestamp = event.block.timestamp
+
+ verifiedProof.save()
+
+ group.save()
+
+ log.info("Verified proof with signal '{}' in the onchain group '{}' has been added", [
+ event.params.signal.toHexString(),
+ group.id
+ ])
+ }
+}
diff --git a/apps/subgraph/src/utils.ts b/apps/subgraph/src/utils.ts
new file mode 100644
index 00000000..0c3549fd
--- /dev/null
+++ b/apps/subgraph/src/utils.ts
@@ -0,0 +1,25 @@
+import { ByteArray, crypto } from "@graphprotocol/graph-ts"
+
+/**
+ * Concatenates two byte arrays.
+ * @param a First byte array.
+ * @param b Second byte array.
+ * @returns Final concatenated byte array.
+ */
+export function concat(a: ByteArray, b: ByteArray): ByteArray {
+ const c = new ByteArray(a.length + b.length)
+
+ c.set(a)
+ c.set(b, a.length)
+
+ return c
+}
+
+/**
+ * Creates a Keccak256 hash.
+ * @param message Message to hash.
+ * @returns Hexadecimal string of the Keccak256 hash.
+ */
+export function hash(message: ByteArray): string {
+ return crypto.keccak256(message).toHexString()
+}
diff --git a/apps/subgraph/subgraph.template.yaml b/apps/subgraph/subgraph.template.yaml
new file mode 100644
index 00000000..434a461a
--- /dev/null
+++ b/apps/subgraph/subgraph.template.yaml
@@ -0,0 +1,35 @@
+specVersion: 0.0.5
+schema:
+ file: ./schema.graphql
+dataSources:
+ - kind: ethereum
+ name: Semaphore
+ network: {{ network }}
+ source:
+ address: "{{ address }}"
+ abi: Semaphore
+ startBlock: {{ startBlock }}
+ mapping:
+ kind: ethereum/events
+ apiVersion: 0.0.7
+ language: wasm/assemblyscript
+ entities:
+ - Group
+ - Member
+ abis:
+ - name: Semaphore
+ file: ./abis/Semaphore.json
+ eventHandlers:
+ - event: GroupCreated(indexed uint256,uint256,uint256)
+ handler: createGroup
+ - event: GroupAdminUpdated(indexed uint256,indexed address,indexed address)
+ handler: updateGroupAdmin
+ - event: MemberAdded(indexed uint256,uint256,uint256,uint256)
+ handler: addMember
+ - event: MemberUpdated(indexed uint256,uint256,uint256,uint256,uint256)
+ handler: updateMember
+ - event: MemberRemoved(indexed uint256,uint256,uint256,uint256)
+ handler: removeMember
+ - event: ProofVerified(indexed uint256,indexed uint256,uint256,indexed uint256,uint256)
+ handler: addVerifiedProof
+ file: ./src/semaphore.ts
diff --git a/apps/subgraph/tests/.bin/semaphore.wasm b/apps/subgraph/tests/.bin/semaphore.wasm
new file mode 100644
index 00000000..30bf3dc8
Binary files /dev/null and b/apps/subgraph/tests/.bin/semaphore.wasm differ
diff --git a/apps/subgraph/tests/semaphore-utils.ts b/apps/subgraph/tests/semaphore-utils.ts
new file mode 100644
index 00000000..38b963f1
--- /dev/null
+++ b/apps/subgraph/tests/semaphore-utils.ts
@@ -0,0 +1,160 @@
+import { newMockEvent } from "matchstick-as"
+import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts"
+import {
+ GroupAdminUpdated,
+ GroupCreated,
+ GroupMerkleTreeDurationUpdated,
+ MemberAdded,
+ MemberRemoved,
+ MemberUpdated,
+ ProofVerified
+} from "../generated/Semaphore/Semaphore"
+
+export function createGroupCreatedEvent(groupId: BigInt, merkleTreeDepth: BigInt, zeroValue: BigInt): GroupCreated {
+ const groupCreatedEvent = changetype(newMockEvent())
+
+ groupCreatedEvent.parameters = []
+
+ groupCreatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
+ groupCreatedEvent.parameters.push(
+ new ethereum.EventParam("merkleTreeDepth", ethereum.Value.fromUnsignedBigInt(merkleTreeDepth))
+ )
+ groupCreatedEvent.parameters.push(
+ new ethereum.EventParam("zeroValue", ethereum.Value.fromUnsignedBigInt(zeroValue))
+ )
+
+ return groupCreatedEvent
+}
+
+export function createGroupAdminUpdatedEvent(groupId: BigInt, oldAdmin: Address, newAdmin: Address): GroupAdminUpdated {
+ const groupAdminUpdatedEvent = changetype(newMockEvent())
+
+ groupAdminUpdatedEvent.parameters = []
+
+ groupAdminUpdatedEvent.parameters.push(
+ new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))
+ )
+ groupAdminUpdatedEvent.parameters.push(new ethereum.EventParam("oldAdmin", ethereum.Value.fromAddress(oldAdmin)))
+ groupAdminUpdatedEvent.parameters.push(new ethereum.EventParam("newAdmin", ethereum.Value.fromAddress(newAdmin)))
+
+ return groupAdminUpdatedEvent
+}
+
+export function createGroupMerkleTreeDurationUpdatedEvent(
+ groupId: BigInt,
+ oldMerkleTreeDuration: BigInt,
+ newMerkleTreeDuration: BigInt
+): GroupMerkleTreeDurationUpdated {
+ const groupMerkleTreeDurationUpdatedEvent = changetype(newMockEvent())
+
+ groupMerkleTreeDurationUpdatedEvent.parameters = []
+
+ groupMerkleTreeDurationUpdatedEvent.parameters.push(
+ new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))
+ )
+ groupMerkleTreeDurationUpdatedEvent.parameters.push(
+ new ethereum.EventParam("oldMerkleTreeDuration", ethereum.Value.fromUnsignedBigInt(oldMerkleTreeDuration))
+ )
+ groupMerkleTreeDurationUpdatedEvent.parameters.push(
+ new ethereum.EventParam("newMerkleTreeDuration", ethereum.Value.fromUnsignedBigInt(newMerkleTreeDuration))
+ )
+
+ return groupMerkleTreeDurationUpdatedEvent
+}
+
+export function createMemberAddedEvent(
+ groupId: BigInt,
+ index: BigInt,
+ identityCommitment: BigInt,
+ merkleTreeRoot: BigInt
+): MemberAdded {
+ const memberAddedEvent = changetype(newMockEvent())
+
+ memberAddedEvent.parameters = []
+
+ memberAddedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
+ memberAddedEvent.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index)))
+ memberAddedEvent.parameters.push(
+ new ethereum.EventParam("identityCommitment", ethereum.Value.fromUnsignedBigInt(identityCommitment))
+ )
+ memberAddedEvent.parameters.push(
+ new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot))
+ )
+
+ return memberAddedEvent
+}
+
+export function createMemberRemovedEvent(
+ groupId: BigInt,
+ index: BigInt,
+ identityCommitment: BigInt,
+ merkleTreeRoot: BigInt
+): MemberRemoved {
+ const memberRemovedEvent = changetype(newMockEvent())
+
+ memberRemovedEvent.parameters = []
+
+ memberRemovedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
+ memberRemovedEvent.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index)))
+ memberRemovedEvent.parameters.push(
+ new ethereum.EventParam("identityCommitment", ethereum.Value.fromUnsignedBigInt(identityCommitment))
+ )
+ memberRemovedEvent.parameters.push(
+ new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot))
+ )
+
+ return memberRemovedEvent
+}
+
+export function createMemberUpdatedEvent(
+ groupId: BigInt,
+ index: BigInt,
+ identityCommitment: BigInt,
+ newIdentityCommitment: BigInt,
+ merkleTreeRoot: BigInt
+): MemberUpdated {
+ const memberUpdatedEvent = changetype(newMockEvent())
+
+ memberUpdatedEvent.parameters = []
+
+ memberUpdatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
+ memberUpdatedEvent.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index)))
+ memberUpdatedEvent.parameters.push(
+ new ethereum.EventParam("identityCommitment", ethereum.Value.fromUnsignedBigInt(identityCommitment))
+ )
+ memberUpdatedEvent.parameters.push(
+ new ethereum.EventParam("newIdentityCommitment", ethereum.Value.fromUnsignedBigInt(newIdentityCommitment))
+ )
+ memberUpdatedEvent.parameters.push(
+ new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot))
+ )
+
+ return memberUpdatedEvent
+}
+
+export function createProofVerifiedEvent(
+ groupId: BigInt,
+ merkleTreeRoot: BigInt,
+ externalNullifier: BigInt,
+ nullifierHash: BigInt,
+ signal: BigInt
+): ProofVerified {
+ const proofVerifiedEvent = changetype(newMockEvent())
+
+ proofVerifiedEvent.parameters = []
+
+ proofVerifiedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
+ proofVerifiedEvent.parameters.push(
+ new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot))
+ )
+ proofVerifiedEvent.parameters.push(
+ new ethereum.EventParam("nullifierHash", ethereum.Value.fromUnsignedBigInt(nullifierHash))
+ )
+ proofVerifiedEvent.parameters.push(
+ new ethereum.EventParam("externalNullifier", ethereum.Value.fromUnsignedBigInt(externalNullifier))
+ )
+
+ proofVerifiedEvent.parameters.push(new ethereum.EventParam("signal", ethereum.Value.fromUnsignedBigInt(signal)))
+
+ return proofVerifiedEvent
+}
diff --git a/apps/subgraph/tests/semaphore.test.ts b/apps/subgraph/tests/semaphore.test.ts
new file mode 100644
index 00000000..64e6d2ef
--- /dev/null
+++ b/apps/subgraph/tests/semaphore.test.ts
@@ -0,0 +1,156 @@
+import { Address, BigInt, ByteArray } from "@graphprotocol/graph-ts"
+import { afterAll, assert, clearStore, describe, test } from "matchstick-as/assembly/index"
+import {
+ addMember,
+ addVerifiedProof,
+ createGroup,
+ removeMember,
+ updateGroupAdmin,
+ updateMember
+} from "../src/semaphore"
+import { concat, hash } from "../src/utils"
+import {
+ createGroupAdminUpdatedEvent,
+ createGroupCreatedEvent,
+ createMemberAddedEvent,
+ createMemberRemovedEvent,
+ createMemberUpdatedEvent,
+ createProofVerifiedEvent
+} from "./semaphore-utils"
+
+// https://thegraph.com/docs/en/developer/matchstick
+describe("Semaphore subgraph", () => {
+ afterAll(() => {
+ clearStore()
+ })
+
+ describe("# createGroup", () => {
+ test("Should have created a group", () => {
+ const groupId = BigInt.fromI32(234)
+ const merkleTreeDepth = BigInt.fromI32(20)
+ const zeroValue = BigInt.fromI32(0)
+ const oldAdmin = Address.fromString("0x0000000000000000000000000000000000000000")
+ const newAdmin = Address.fromString("0x0000000000000000000000000000000000000001")
+
+ const event1 = createGroupCreatedEvent(groupId, merkleTreeDepth, zeroValue)
+ const event2 = createGroupAdminUpdatedEvent(groupId, oldAdmin, newAdmin)
+
+ createGroup(event1)
+ updateGroupAdmin(event2)
+
+ assert.entityCount("Group", 1)
+ assert.entityCount("MerkleTree", 1)
+
+ assert.fieldEquals("Group", groupId.toString(), "admin", "0x0000000000000000000000000000000000000001")
+ assert.fieldEquals("Group", groupId.toString(), "merkleTree", groupId.toString())
+
+ assert.fieldEquals("MerkleTree", groupId.toString(), "depth", "20")
+ assert.fieldEquals("MerkleTree", groupId.toString(), "zeroValue", "0")
+ assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "0")
+ assert.fieldEquals("MerkleTree", groupId.toString(), "group", groupId.toString())
+ })
+ })
+
+ describe("# updateGroupAdmin", () => {
+ test("Should have updated a group admin", () => {
+ const groupId = BigInt.fromI32(234)
+ const oldAdmin = Address.fromString("0x0000000000000000000000000000000000000001")
+ const newAdmin = Address.fromString("0x0000000000000000000000000000000000000002")
+
+ const event = createGroupAdminUpdatedEvent(groupId, oldAdmin, newAdmin)
+
+ updateGroupAdmin(event)
+
+ assert.fieldEquals("Group", groupId.toString(), "admin", "0x0000000000000000000000000000000000000002")
+ })
+ })
+
+ describe("# addMember", () => {
+ test("Should have added a group member", () => {
+ const groupId = BigInt.fromI32(234)
+ const index = BigInt.fromI32(0)
+ const identityCommitment = BigInt.fromI32(123)
+ const merkleTreeRoot = BigInt.fromI32(999)
+ const id = hash(concat(ByteArray.fromBigInt(index), ByteArray.fromBigInt(groupId)))
+
+ const event = createMemberAddedEvent(groupId, index, identityCommitment, merkleTreeRoot)
+
+ addMember(event)
+
+ assert.entityCount("Member", 1)
+
+ assert.fieldEquals("Member", id, "index", "0")
+ assert.fieldEquals("Member", id, "identityCommitment", "123")
+ assert.fieldEquals("Member", id, "group", groupId.toString())
+
+ assert.fieldEquals("MerkleTree", groupId.toString(), "root", "999")
+ assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "1")
+ })
+ })
+
+ describe("# updateMember", () => {
+ test("Should have added a group member", () => {
+ const groupId = BigInt.fromI32(234)
+ const index = BigInt.fromI32(0)
+ const identityCommitment = BigInt.fromI32(123)
+ const newIdentityCommitment = BigInt.fromI32(124)
+ const merkleTreeRoot = BigInt.fromI32(1000)
+ const id = hash(concat(ByteArray.fromBigInt(index), ByteArray.fromBigInt(groupId)))
+
+ const event = createMemberUpdatedEvent(
+ groupId,
+ index,
+ identityCommitment,
+ newIdentityCommitment,
+ merkleTreeRoot
+ )
+
+ updateMember(event)
+
+ assert.fieldEquals("Member", id, "identityCommitment", "124")
+
+ assert.fieldEquals("MerkleTree", groupId.toString(), "root", "1000")
+ })
+ })
+
+ describe("# removeMember", () => {
+ test("Should have removed a group member", () => {
+ const groupId = BigInt.fromI32(234)
+ const index = BigInt.fromI32(0)
+ const identityCommitment = BigInt.fromI32(123)
+ const merkleTreeRoot = BigInt.fromI32(1001)
+ const id = hash(concat(ByteArray.fromBigInt(index), ByteArray.fromBigInt(groupId)))
+
+ const event = createMemberRemovedEvent(groupId, index, identityCommitment, merkleTreeRoot)
+
+ removeMember(event)
+
+ assert.fieldEquals("Member", id, "identityCommitment", "0")
+
+ assert.fieldEquals("MerkleTree", groupId.toString(), "root", "1001")
+ })
+ })
+
+ describe("# addVerifiedProof", () => {
+ test("Should have added a proof", () => {
+ const groupId = BigInt.fromI32(234)
+ const merkleTreeRoot = BigInt.fromI32(1001)
+ const externalNullifier = BigInt.fromI32(1)
+ const nullifierHash = BigInt.fromI32(666)
+ const signal = BigInt.fromI32(2)
+ const id = hash(concat(ByteArray.fromBigInt(nullifierHash), ByteArray.fromBigInt(groupId)))
+
+ const event = createProofVerifiedEvent(groupId, merkleTreeRoot, externalNullifier, nullifierHash, signal)
+
+ addVerifiedProof(event)
+
+ assert.entityCount("VerifiedProof", 1)
+
+ assert.fieldEquals("VerifiedProof", id, "merkleTreeRoot", "1001")
+ assert.fieldEquals("VerifiedProof", id, "externalNullifier", "1")
+ assert.fieldEquals("VerifiedProof", id, "nullifierHash", "666")
+ assert.fieldEquals("VerifiedProof", id, "signal", "2")
+ assert.fieldEquals("VerifiedProof", id, "group", groupId.toString())
+ })
+ })
+})
diff --git a/apps/subgraph/tsconfig.json b/apps/subgraph/tsconfig.json
new file mode 100644
index 00000000..1c33f88a
--- /dev/null
+++ b/apps/subgraph/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json",
+ "include": ["src", "scripts", "tests"]
+}
diff --git a/package.json b/package.json
index 3dd2b642..7f5851ba 100644
--- a/package.json
+++ b/package.json
@@ -8,13 +8,15 @@
"private": true,
"scripts": {
"build:libraries": "yarn workspaces foreach -t --no-private run build",
- "compile:contracts": "yarn workspace contracts compile",
+ "build:subgraph": "yarn workspace semaphore-subgraph codegen goerli && yarn workspace semaphore-subgraph build",
+ "compile:contracts": "yarn workspace semaphore-contracts compile",
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
"remove:template-files": "ts-node scripts/remove-template-files.ts",
- "test": "yarn test:libraries && yarn test:contracts",
+ "test": "yarn test:libraries && yarn test:contracts && yarn test:subgraph",
"test:libraries": "jest --coverage",
- "test:contracts": "yarn workspace contracts test:coverage",
- "lint": "eslint . --ext .js,.ts,.tsx && yarn workspace contracts lint",
+ "test:subgraph": "yarn workspace semaphore-subgraph test",
+ "test:contracts": "yarn workspace semaphore-contracts test:coverage",
+ "lint": "eslint . --ext .js,.ts,.tsx && yarn workspace semaphore-contracts lint",
"prettier": "prettier -c .",
"prettier:write": "prettier -w .",
"docs": "typedoc --cname js.semaphore.pse.dev --githubPages true",
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 783d8027..9eeb023c 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -1,5 +1,5 @@
{
- "name": "contracts",
+ "name": "semaphore-contracts",
"private": true,
"scripts": {
"start": "hardhat node",
diff --git a/yarn.lock.REMOVED.git-id b/yarn.lock.REMOVED.git-id
index 97f0b603..50a16600 100644
--- a/yarn.lock.REMOVED.git-id
+++ b/yarn.lock.REMOVED.git-id
@@ -1 +1 @@
-27e13e856d828fdf7ba52e07404ed16ca3a0b6f3
\ No newline at end of file
+90c3d691290740e8dfd7b75dd1a43eca7d45a826
\ No newline at end of file