diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.env.development b/packages/cli-template-monorepo-ethers/apps/web-app/.env.development index 1d41062f3..1bed8b578 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/.env.development +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.env.development @@ -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 diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.env.production b/packages/cli-template-monorepo-ethers/apps/web-app/.env.production index fa2cbb454..1bea727d0 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/.env.production +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.env.production @@ -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= diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore b/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore index d183c4f53..55811698f 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore @@ -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 diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json b/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json index 123448e9e..5b51c7a42 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json +++ b/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json @@ -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": {} } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs b/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs index 2233f10f5..ae9ad74ec 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs +++ b/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs @@ -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 } }) diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts index 795b2c5dd..45698f55c 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts @@ -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" diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts index 608055f9e..1d0ee24b9 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts @@ -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" diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css index 505ae8579..738c3fa56 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css @@ -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-stepper:hover { + text-decoration: underline; } -.button-link { +.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,55 @@ 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,11 +351,11 @@ a { border-right: 2px solid transparent; animation: spin 1s linear infinite; z-index: 20; - margin-left: 1rem; + margin-left: 0.5rem; } @keyframes spin { to { transform: rotate(360deg); } -} +} \ No newline at end of file diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/group/page.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/group/page.tsx new file mode 100644 index 000000000..28dfc4465 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/group/page.tsx @@ -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 ( + <> +

Groups

+ +

+ + Semaphore groups + {" "} + are{" "} + + Lean incremental Merkle trees + {" "} + in which each leaf contains an identity commitment for a user. Groups can be abstracted to represent + events, polls, or organizations. +

+ +
+ +
+

Group users ({_users.length})

+ +
+ + {_users.length > 0 && ( +
+ {users.map((user, i) => ( +
+

+ {_identity?.commitment.toString() === user ? {user} : user} +

+
+ ))} +
+ )} + +
+ +
+ +
+ + router.push("/")} + onNextClick={userHasJoined ? () => router.push("/proofs") : undefined} + /> + + ) +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/groups/page.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/groups/page.tsx deleted file mode 100644 index 8e72752ef..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/groups/page.tsx +++ /dev/null @@ -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() - - 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 ( - <> -

Groups

- -

- - Semaphore groups - {" "} - are{" "} - - Lean incremental Merkle trees - {" "} - in which each leaf contains an identity commitment for a user. Groups can be abstracted to represent - events, polls, or organizations. -

- -
- -
-

Group users ({_users.length})

- -
- -
- -
- - {_users.length > 0 && ( -
- {_users.map((user, i) => ( -
-

{user.toString()}

-
- ))} -
- )} - -
- - router.push("/")} - onNextClick={_identity && userHasJoined(_identity) ? () => router.push("/proofs") : undefined} - /> - - ) -} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx index e6a6d1aac..138bb727a 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx @@ -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 ( + + + + + - {children} + + + {children} + + ) diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx index 1469af1e5..ee800be1f 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx @@ -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() 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 ( <> -

Identities

+

Identities

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.

-
+
-
+

Identity

- {_identity && ( - - )}
- {_identity ? ( -
-
-

Private Key: {_identity.privateKey.toString()}

-

Commitment: {_identity.commitment.toString()}

-
-
- ) : ( -
- + {_identity && ( +
+

+ Private Key (base64):
{_identity.export()} +

+

+ Public Key:
[{_identity.publicKey[0].toString()},{" "} + {_identity.publicKey[1].toString()}] +

+

+ Commitment:
{_identity.commitment.toString()} +

)} -
+
+ +
+ +
- router.push("/groups"))} /> + router.push("/group"))} /> ) } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx index de060019a..0ee9a92fd 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx @@ -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() - - 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() {
-

Feedback messages ({_feedback.length})

-
-
- -
- - {_feedback.length > 0 && ( -
- {_feedback.map((f, i) => ( + {feedback.length > 0 && ( +
+ {feedback.map((f, i) => (

{f}

@@ -145,9 +166,16 @@ export default function ProofsPage() {
)} +
+ +
+
- router.push("/groups")} /> + router.push("/group")} /> ) } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx index 63b1499ee..98e441b50 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx @@ -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("") + 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 ( -
+ <>
-
- - - {children} - - -
+
{children}
-
+
- {_logs.endsWith("...")} -

{_logs || `Current step: ${pathname}`}

+ {log.endsWith("...")} +

{log || `Current step: ${pathname}`}

-
+ ) } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/components/Stepper.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/components/Stepper.tsx index 55cc89330..ab10ba036 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/components/Stepper.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/components/Stepper.tsx @@ -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 (
{onPrevClick !== undefined ? ( - ) : ( - + )}

{step.toString()}/3

{onNextClick !== undefined ? ( - ) : ( - + )}
) diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/context/LogContext.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/context/LogContext.tsx new file mode 100644 index 000000000..8885447f1 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/context/LogContext.tsx @@ -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(null) + +interface ProviderProps { + children: ReactNode +} + +export const LogContextProvider: React.FC = ({ children }) => { + const [log, setLog] = useState("") + + return ( + + {children} + + ) +} + +export const useLogContext = () => { + const context = useContext(LogContext) + if (context === null) { + throw new Error("LogContext must be used within a LogContextProvider") + } + return context +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/context/LogsContext.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/context/LogsContext.ts deleted file mode 100644 index 5f5e17692..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/context/LogsContext.ts +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react" - -export type LogsContextType = { - _logs: string - setLogs: (logs: string) => void -} - -export default React.createContext({ - _logs: "", - setLogs: (logs: string) => logs -}) diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/context/SemaphoreContext.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/context/SemaphoreContext.ts deleted file mode 100644 index 4cac350ed..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/context/SemaphoreContext.ts +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react" - -export type SemaphoreContextType = { - _users: string[] - _feedback: string[] - refreshUsers: () => Promise - addUser: (user: string) => void - refreshFeedback: () => Promise - addFeedback: (feedback: string) => void -} - -export default React.createContext({ - _users: [], - _feedback: [], - refreshUsers: () => Promise.resolve(), - addUser: () => {}, - refreshFeedback: () => Promise.resolve(), - addFeedback: () => {} -}) diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/context/SemaphoreContext.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/context/SemaphoreContext.tsx new file mode 100644 index 000000000..425b545ef --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/context/SemaphoreContext.tsx @@ -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 + addUser: (user: string) => void + refreshFeedback: () => Promise + addFeedback: (feedback: string) => void +} + +const SemaphoreContext = createContext(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 = ({ children }) => { + const [_users, setUsers] = useState([]) + const [_feedback, setFeedback] = useState([]) + + const refreshUsers = useCallback(async (): Promise => { + 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 => { + 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 ( + + {children} + + ) +} + +export const useSemaphoreContext = () => { + const context = useContext(SemaphoreContext) + if (context === null) { + throw new Error("SemaphoreContext must be used within a SemaphoreContextProvider") + } + return context +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts deleted file mode 100644 index 4f5dab63e..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts +++ /dev/null @@ -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([]) - const [_feedback, setFeedback] = useState([]) - - const refreshUsers = useCallback(async (): Promise => { - 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 => { - 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 - } -} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphoreIdentity.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphoreIdentity.ts new file mode 100644 index 000000000..26e2ba773 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphoreIdentity.ts @@ -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() + + useEffect(() => { + const privateKey = localStorage.getItem("identity") + + if (!privateKey) { + router.push("/") + return + } + + setIdentity(new Identity(privateKey)) + }, [router]) + + return { + _identity + } +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development index 1d41062f3..1bed8b578 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development @@ -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 diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production index fa2cbb454..1bea727d0 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production @@ -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= diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore b/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore index d183c4f53..55811698f 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore @@ -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 diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json b/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json index 123448e9e..5b51c7a42 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json @@ -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": {} } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs b/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs index 2233f10f5..ae9ad74ec 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs @@ -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 } }) diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts index 795b2c5dd..45698f55c 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts @@ -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" diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts index 608055f9e..1d0ee24b9 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts @@ -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" diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css index 505ae8579..738c3fa56 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css @@ -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-stepper:hover { + text-decoration: underline; } -.button-link { +.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,55 @@ 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,11 +351,11 @@ a { border-right: 2px solid transparent; animation: spin 1s linear infinite; z-index: 20; - margin-left: 1rem; + margin-left: 0.5rem; } @keyframes spin { to { transform: rotate(360deg); } -} +} \ No newline at end of file diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/group/page.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/group/page.tsx new file mode 100644 index 000000000..28dfc4465 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/group/page.tsx @@ -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 ( + <> +

Groups

+ +

+ + Semaphore groups + {" "} + are{" "} + + Lean incremental Merkle trees + {" "} + in which each leaf contains an identity commitment for a user. Groups can be abstracted to represent + events, polls, or organizations. +

+ +
+ +
+

Group users ({_users.length})

+ +
+ + {_users.length > 0 && ( +
+ {users.map((user, i) => ( +
+

+ {_identity?.commitment.toString() === user ? {user} : user} +

+
+ ))} +
+ )} + +
+ +
+ +
+ + router.push("/")} + onNextClick={userHasJoined ? () => router.push("/proofs") : undefined} + /> + + ) +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx index e6a6d1aac..138bb727a 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx @@ -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 ( + + + + + - {children} + + + {children} + + ) diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx index 1469af1e5..ee800be1f 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx @@ -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() 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 ( <> -

Identities

+

Identities

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.

-
+
-
+

Identity

- {_identity && ( - - )}
- {_identity ? ( -
-
-

Private Key: {_identity.privateKey.toString()}

-

Commitment: {_identity.commitment.toString()}

-
-
- ) : ( -
- + {_identity && ( +
+

+ Private Key (base64):
{_identity.export()} +

+

+ Public Key:
[{_identity.publicKey[0].toString()},{" "} + {_identity.publicKey[1].toString()}] +

+

+ Commitment:
{_identity.commitment.toString()} +

)} -
+
+ +
+ +
- router.push("/groups"))} /> + router.push("/group"))} /> ) } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx index de060019a..0ee9a92fd 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx @@ -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() - - 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() {
-

Feedback messages ({_feedback.length})

-
-
- -
- - {_feedback.length > 0 && ( -
- {_feedback.map((f, i) => ( + {feedback.length > 0 && ( +
+ {feedback.map((f, i) => (

{f}

@@ -145,9 +166,16 @@ export default function ProofsPage() {
)} +
+ +
+
- router.push("/groups")} /> + router.push("/group")} /> ) } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/providers.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/providers.tsx new file mode 100644 index 000000000..910643fc2 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/providers.tsx @@ -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 ( + + {children} + + ) +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx index 63b1499ee..f4240ef7b 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx @@ -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("") + 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 ( -
+ <>
-
- - - {children} - - -
+
{children}
-
+
- {_logs.endsWith("...")} -

{_logs || `Current step: ${pathname}`}

+ {log.endsWith("...")} +

{log || `Current step: ${pathname}`}

-
+ ) } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/Stepper.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/Stepper.tsx index 55cc89330..ab10ba036 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/Stepper.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/Stepper.tsx @@ -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 (
{onPrevClick !== undefined ? ( - ) : ( - + )}

{step.toString()}/3

{onNextClick !== undefined ? ( - ) : ( - + )}
) diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/LogContext.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/LogContext.tsx new file mode 100644 index 000000000..8885447f1 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/LogContext.tsx @@ -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(null) + +interface ProviderProps { + children: ReactNode +} + +export const LogContextProvider: React.FC = ({ children }) => { + const [log, setLog] = useState("") + + return ( + + {children} + + ) +} + +export const useLogContext = () => { + const context = useContext(LogContext) + if (context === null) { + throw new Error("LogContext must be used within a LogContextProvider") + } + return context +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/LogsContext.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/LogsContext.ts deleted file mode 100644 index 5f5e17692..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/LogsContext.ts +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react" - -export type LogsContextType = { - _logs: string - setLogs: (logs: string) => void -} - -export default React.createContext({ - _logs: "", - setLogs: (logs: string) => logs -}) diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/SemaphoreContext.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/SemaphoreContext.ts deleted file mode 100644 index 4cac350ed..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/SemaphoreContext.ts +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react" - -export type SemaphoreContextType = { - _users: string[] - _feedback: string[] - refreshUsers: () => Promise - addUser: (user: string) => void - refreshFeedback: () => Promise - addFeedback: (feedback: string) => void -} - -export default React.createContext({ - _users: [], - _feedback: [], - refreshUsers: () => Promise.resolve(), - addUser: () => {}, - refreshFeedback: () => Promise.resolve(), - addFeedback: () => {} -}) diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/SemaphoreContext.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/SemaphoreContext.tsx new file mode 100644 index 000000000..fd67bcf29 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/context/SemaphoreContext.tsx @@ -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 + addUser: (user: string) => void + refreshFeedback: () => Promise + addFeedback: (feedback: string) => void +} + +const SemaphoreContext = createContext(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 = ({ children }) => { + const [_users, setUsers] = useState([]) + const [_feedback, setFeedback] = useState([]) + + const refreshUsers = useCallback(async (): Promise => { + 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 => { + 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 ( + + {children} + + ) +} + +export const useSemaphoreContext = () => { + const context = useContext(SemaphoreContext) + if (context === null) { + throw new Error("SemaphoreContext must be used within a SemaphoreContextProvider") + } + return context +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts deleted file mode 100644 index 7af264f82..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { SemaphoreSubgraph } 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([]) - const [_feedback, setFeedback] = useState([]) - - const refreshUsers = useCallback(async (): Promise => { - const semaphore = new SemaphoreSubgraph(ethereumNetwork) - - const group = await semaphore.getGroup(process.env.NEXT_PUBLIC_GROUP_ID as string, { members: true }) - - setUsers(group.members!) - }, []) - - const addUser = useCallback( - (user: any) => { - setUsers([..._users, user]) - }, - [_users] - ) - - const refreshFeedback = useCallback(async (): Promise => { - const semaphore = new SemaphoreSubgraph(ethereumNetwork) - - const group = await semaphore.getGroup(process.env.NEXT_PUBLIC_GROUP_ID as string, { - validatedProofs: true - }) - - setFeedback(group.validatedProofs!.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32)))) - }, []) - - const addFeedback = useCallback( - (feedback: string) => { - setFeedback([..._feedback, feedback]) - }, - [_feedback] - ) - - return { - _users, - _feedback, - refreshUsers, - addUser, - refreshFeedback, - addFeedback - } -} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphoreIdentity.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphoreIdentity.ts new file mode 100644 index 000000000..26e2ba773 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphoreIdentity.ts @@ -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() + + useEffect(() => { + const privateKey = localStorage.getItem("identity") + + if (!privateKey) { + router.push("/") + return + } + + setIdentity(new Identity(privateKey)) + }, [router]) + + return { + _identity + } +}