From 054bcae77d7fa1cd3cb2b73b5e791fd1b5359a41 Mon Sep 17 00:00:00 2001 From: Vojtech Simetka Date: Thu, 11 May 2023 15:25:53 +0200 Subject: [PATCH] feat: local deployment steps (#413) --- README.md | 70 ++++++++++++++++- packages/contracts/hardhat.config.ts | 33 +++----- packages/contracts/package.json | 2 +- packages/contracts/scripts/deploy-unirep.ts | 12 --- packages/contracts/tasks/deploy.ts | 58 ++++++++------ .../contracts/test/GlobalAnonymousFeed.ts | 2 +- packages/relayer/.env.example | 4 +- packages/relayer/src/app.ts | 6 +- packages/relayer/src/config/index.ts | 6 +- packages/relayer/src/routes/root.ts | 10 +-- packages/relayer/src/services/epoch.ts | 6 +- packages/ui/.env | 4 +- packages/ui/src/lib/adapters/utils.ts | 7 +- .../lib/adapters/zkitter-god-mode/index.ts | 13 +--- .../src/lib/adapters/zkitter/group-adapter.ts | 2 +- packages/ui/src/lib/adapters/zkitter/index.ts | 75 +++++++++++-------- packages/ui/src/lib/adapters/zkitter/utils.ts | 2 +- .../routes/persona/draft/[id]/+page.svelte | 22 +++++- 18 files changed, 205 insertions(+), 129 deletions(-) delete mode 100644 packages/contracts/scripts/deploy-unirep.ts diff --git a/README.md b/README.md index 38d046f0..4d36c138 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,76 @@ Also, curating and creating Players earn reputation (REP) for positive gameplay We are using ZK-Proof technology and [Waku](https://waku.org/) to ensure privacy, with a hat-tip to [Unirep](https://medium.com/privacy-scaling-explorations/unirep-a-private-and-non-repudiable-reputation-system-7fb5c6478549), and [Semaphore](https://semaphore.appliedzkp.org/). ## For Developers -**Would you like to launch and play with Kurate locally?** +0. Install all dependencies +```sh +pnpm i +``` + +1. Start blockchain and deploy contracts +```sh +cd packages/contracts +``` + +```sh +pnpm start:blockchain +``` + +In another terminal window, compile, deploy the contracts +``` +pnpm start +``` + +If successfully, the output should say: +``` +GlobalAnonymousFeedContract contract has been deployed +Don't forget to set the variables for both the UI and relayer + +PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +PUBLIC_PROVIDER=http://localhost:8545 + +Relayer only +PRIVATE_KEY=... + +UI only +PUBLIC_RELAYER_URL=... +``` + +2. Start relayer +```sh +cd packages/contracts +``` +Set the environment variables according to the contract deployment (for private key you can use any hardhat key). Should be: +```sh +PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +PUBLIC_PROVIDER=http://localhost:8545 +PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +``` + +Build and start the relayer +``` +pnpm build +pnpm start +``` + +3. Start UI +```sh +cd packages/ui +``` + +Set the environment variables according to the contract deployment and where the relayer lives: Should be: +```sh +PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +PUBLIC_PROVIDER=http://localhost:8545 +PUBLIC_RELAYER_URL=http://localhost:3000 +``` + +Start the UI with +```sh +pnpm dev +``` + +You can now open the app at http://localhost:5173/ . Just make sure you are using either the `zkitter` or the `zkitter-god-node` adapter. You can configure those in `/dev` route (http://localhost:5173/dev) **Are you interested in contributing to Kurate?** diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 3248c807..3caabb9c 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -14,35 +14,25 @@ import "./tasks/deploy" dotenvConfig({ path: resolve(__dirname, "../../.env") }) function getNetworks(): NetworksUserConfig { - if (process.env.ETHEREUM_URL && process.env.ETHEREUM_PRIVATE_KEY) { - const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`] + const networks: NetworksUserConfig = { + localhost: { + url: 'http://127.0.0.1:8545', + chainId: 31337 + } + } + if (process.env.ETHEREUM_URL && process.env.ETHEREUM_PRIVATE_KEY) { return { - goerli: { - url: process.env.ETHEREUM_URL, - chainId: 5, - accounts - }, + ...networks, // arbitrum goerli agor: { url: 'https://goerli-rollup.arbitrum.io/rpc', chainId: 421613, - accounts, - }, - sepolia: { - url: process.env.ETHEREUM_URL, - chainId: 11155111, - accounts + accounts: [process.env.ETHEREUM_PRIVATE_KEY], }, - localhost: { - url: 'http://127.0.0.1:7545', - chainId: 1337, - accounts, - } } } - - return {} + return networks } const hardhatConfig: HardhatUserConfig = { @@ -54,9 +44,6 @@ const hardhatConfig: HardhatUserConfig = { artifacts: config.paths.build.contracts }, networks: { - hardhat: { - chainId: 1337 - }, ...getNetworks() }, gasReporter: { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index fd16bb45..6da213fb 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -4,7 +4,7 @@ "private": true, "main": "index.js", "scripts": { - "start": "pnpm run compile && pnpm run deploy -- --network localhost", + "start": "pnpm run compile && pnpm run deploy --network localhost", "start:blockchain": "hardhat node", "compile": "hardhat compile", "download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts", diff --git a/packages/contracts/scripts/deploy-unirep.ts b/packages/contracts/scripts/deploy-unirep.ts deleted file mode 100644 index 0b43e2c2..00000000 --- a/packages/contracts/scripts/deploy-unirep.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {ethers} from 'ethers' -import { Unirep } from '@unirep/contracts' -import { deployUnirep } from '@unirep/contracts/deploy' - -const privateKey = process.env.ETHEREUM_PRIVATE_KEY as string; -const provider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_URL); -const deployer = new ethers.Wallet(privateKey, provider); - -(async () => { - const unirepContract: Unirep = await deployUnirep(deployer) - console.log(unirepContract); -})(); diff --git a/packages/contracts/tasks/deploy.ts b/packages/contracts/tasks/deploy.ts index dd756737..2044d7c6 100644 --- a/packages/contracts/tasks/deploy.ts +++ b/packages/contracts/tasks/deploy.ts @@ -1,43 +1,57 @@ +import { Unirep } from "@unirep/contracts"; +import { deployUnirep } from "@unirep/contracts/deploy"; import { task, types } from "hardhat/config" +// 28800 seconds = 8 hours per epoch +const DEFAULT_EPOCH_LENGTH = 28800; + task("deploy", "Deploy a GlobalAnonymousFeed contract") .addOptionalParam("unirep", "unirep contract address", undefined, types.string) .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ logs, unirep: unirepAddress }, { ethers, run }) => { + .addOptionalParam("epoch", `Epoch length (defaults to ${DEFAULT_EPOCH_LENGTH}`, DEFAULT_EPOCH_LENGTH, types.int) + .setAction(async ({ logs, unirep: unirepAddress, epoch: epochLength }, { ethers }) => { const globalAnonymousFeedFactory = await ethers.getContractFactory("GlobalAnonymousFeed"); const [deployer] = await ethers.getSigners(); - console.log( - "Deploying contracts with the account:", - deployer.address - ); + logs && console.log(`Deploying contracts with the account: ${deployer.address}`); + logs && console.log("Account balance:", ethers.utils.formatEther(await deployer.getBalance())); + + if (!unirepAddress) { + logs && console.log("Unirep contract address not provided, deploying Unirep first\n") + const unirepContract: Unirep = await deployUnirep(deployer) + unirepAddress = unirepContract.address; + } - console.log("Account balance:", (await deployer.getBalance()).toString()); + logs && console.log(`\nUnirep contract address: ${unirepAddress}`); const gasPrice = await globalAnonymousFeedFactory.signer.getGasPrice(); - // const estimatedGas = await globalAnonymousFeedFactory.signer.estimateGas(globalAnonymousFeedFactory.getDeployTransaction('0xF309DDf2Cc1b2701fED5171C5150092bAc946f07', 28800)); - const estimatedGas = await globalAnonymousFeedFactory.signer.estimateGas(globalAnonymousFeedFactory.getDeployTransaction('0x5e5384c3EA26185BADF41d6980397eB4D36b850e', 60)); - console.log(`Estimated gas: ${estimatedGas}`); - console.log(`Gas Price: ${gasPrice}`) + const estimatedGas = await globalAnonymousFeedFactory.signer.estimateGas(globalAnonymousFeedFactory.getDeployTransaction(unirepAddress, epochLength)); + logs && console.log(`Estimated gas: ${estimatedGas}`); + logs && console.log(`Gas price: ${gasPrice}`) + const deploymentPrice = gasPrice.mul(estimatedGas); const deployerBalance = await globalAnonymousFeedFactory.signer.getBalance(); - console.log(`Deployer balance: ${ethers.utils.formatEther(deployerBalance)}`); - console.log(`Deployment price: ${ethers.utils.formatEther(deploymentPrice)}`); - // This is unirep@2.0.0-beta-1 contract address on Arbitrum Goerli - // https://developer.unirep.io/docs/testnet-deployment - // 28800 seconds = 8 hours per epoch - // const globalAnonymousFeedContract = await globalAnonymousFeedFactory.deploy('0xF309DDf2Cc1b2701fED5171C5150092bAc946f07', 28800); - // This was used for local dev - // 60 seconds = 1 minute per epoch - const globalAnonymousFeedContract = await globalAnonymousFeedFactory.deploy('0x5e5384c3EA26185BADF41d6980397eB4D36b850e', 60); + logs && console.log(`Deployer balance: ${ethers.utils.formatEther(deployerBalance)}`); + logs && console.log(`Deployment price: ${ethers.utils.formatEther(deploymentPrice)}`); + + const globalAnonymousFeedContract = await globalAnonymousFeedFactory.deploy(unirepAddress, epochLength); await globalAnonymousFeedContract.deployed(); - if (logs) { - console.info(`GlobalAnonymousFeedContract contract has been deployed to: ${globalAnonymousFeedContract.address}`); - } + logs && console.info("\n-----------------------------------------------------------------"); + logs && console.info('GlobalAnonymousFeedContract contract has been deployed'); + logs && console.log("Don't forget to set the variables for both the UI and relayer\n"); + + logs && console.log(`PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=${globalAnonymousFeedContract.address}`); + logs && console.log(`PUBLIC_PROVIDER=${ethers.provider.connection.url}`); + + logs && console.log("\nRelayer only"); + logs && console.log(`PRIVATE_KEY=...`); + + logs && console.log("\nUI only"); + logs && console.log(`PUBLIC_RELAYER_URL=...`); return globalAnonymousFeedContract; }) diff --git a/packages/contracts/test/GlobalAnonymousFeed.ts b/packages/contracts/test/GlobalAnonymousFeed.ts index d8e9d749..2a7b0fef 100644 --- a/packages/contracts/test/GlobalAnonymousFeed.ts +++ b/packages/contracts/test/GlobalAnonymousFeed.ts @@ -62,7 +62,7 @@ describe("Global Anonymous Feed Contract", () => { before(async () => { provider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_URL) const signer = new ethers.Wallet(process.env.ETHEREUM_PRIVATE_KEY as string, provider) - postContract = new GlobalAnonymousFeed__factory(signer).attach(process.env.GLOBAL_ANONYMOUS_FEED_ADDRESS as string); + postContract = new GlobalAnonymousFeed__factory(signer).attach(process.env.PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS as string); unirepContract = getUnirepContract('0x5e5384c3EA26185BADF41d6980397eB4D36b850e', signer); // unirepContract = getUnirepContract('0xF309DDf2Cc1b2701fED5171C5150092bAc946f07', signer); // signupVerifier = new ethers.Contract( diff --git a/packages/relayer/.env.example b/packages/relayer/.env.example index 8eb4dc98..ece9d581 100644 --- a/packages/relayer/.env.example +++ b/packages/relayer/.env.example @@ -1,3 +1,3 @@ -RPC_URL=https://goerli-rollup.arbitrum.io/rpc -GLOBAL_ANONYMOUS_FEED_ADDRESS=0x0 +PUBLIC_PROVIDER=https://goerli-rollup.arbitrum.io/rpc +PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0x0 PRIVATE_KEY= diff --git a/packages/relayer/src/app.ts b/packages/relayer/src/app.ts index 455ce2e3..bc2fd199 100644 --- a/packages/relayer/src/app.ts +++ b/packages/relayer/src/app.ts @@ -3,7 +3,7 @@ import AutoLoad, { AutoloadPluginOptions } from "@fastify/autoload"; import { FastifyPluginAsync } from "fastify"; import { JsonSchemaToTsProvider } from "@fastify/type-provider-json-schema-to-ts"; import { syncGroup } from "./services/rln"; -import { GLOBAL_ANONYMOUS_FEED_ADDRESS, RPC_URL } from "./config"; +import { PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, PUBLIC_PROVIDER } from "./config"; import { getDefaultProvider } from "@ethersproject/providers"; import epochSealer from "./services/epoch"; @@ -22,8 +22,8 @@ const app: FastifyPluginAsync = async ( fastify.withTypeProvider(); // Services - const provider = getDefaultProvider(RPC_URL); - syncGroup(provider, GLOBAL_ANONYMOUS_FEED_ADDRESS); + const provider = getDefaultProvider(PUBLIC_PROVIDER); + syncGroup(provider, PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS); epochSealer.start(); diff --git a/packages/relayer/src/config/index.ts b/packages/relayer/src/config/index.ts index 9a3aff1d..3e6cd0bf 100644 --- a/packages/relayer/src/config/index.ts +++ b/packages/relayer/src/config/index.ts @@ -3,9 +3,9 @@ import { config } from "dotenv"; config(); const { - GLOBAL_ANONYMOUS_FEED_ADDRESS = "", - RPC_URL = "", + PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS = "", + PUBLIC_PROVIDER = "", PRIVATE_KEY = "", } = process.env; -export { GLOBAL_ANONYMOUS_FEED_ADDRESS, RPC_URL, PRIVATE_KEY }; +export { PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, PUBLIC_PROVIDER, PRIVATE_KEY }; diff --git a/packages/relayer/src/routes/root.ts b/packages/relayer/src/routes/root.ts index adab7444..b914365c 100644 --- a/packages/relayer/src/routes/root.ts +++ b/packages/relayer/src/routes/root.ts @@ -2,7 +2,7 @@ import { FastifyPluginAsyncJsonSchemaToTs } from "@fastify/type-provider-json-sc // import { rlnRegistry, verifyProof } from "../services/rln"; import { getDefaultProvider } from "@ethersproject/providers"; import { Wallet } from "@ethersproject/wallet"; -import { GLOBAL_ANONYMOUS_FEED_ADDRESS, PRIVATE_KEY, RPC_URL } from "../config"; +import { PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, PRIVATE_KEY, PUBLIC_PROVIDER } from "../config"; import { GlobalAnonymousFeed__factory } from "../abi"; import cors from '@fastify/cors' const path = require('path') @@ -331,11 +331,11 @@ const getBodySchemaWithoutRep = () => { const root: FastifyPluginAsyncJsonSchemaToTs = async ( fastify ): Promise => { - console.log(RPC_URL) - const provider = getDefaultProvider(RPC_URL); + console.log(PUBLIC_PROVIDER) + const provider = getDefaultProvider(PUBLIC_PROVIDER); const wallet = new Wallet(PRIVATE_KEY, provider); const feed = GlobalAnonymousFeed__factory.connect( - GLOBAL_ANONYMOUS_FEED_ADDRESS, + PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, wallet ); @@ -360,7 +360,7 @@ const root: FastifyPluginAsyncJsonSchemaToTs = async ( return } const hostname = new URL(origin as string).hostname - if(hostname === "localhost"){ + if(hostname === "localhost" || hostname === "127.0.0.1" || hostname === "kurate.vercel.app"){ // Request from localhost will pass cb(null, true) return diff --git a/packages/relayer/src/services/epoch.ts b/packages/relayer/src/services/epoch.ts index ed61135c..63177e5e 100644 --- a/packages/relayer/src/services/epoch.ts +++ b/packages/relayer/src/services/epoch.ts @@ -1,5 +1,5 @@ import {getDefaultProvider} from "@ethersproject/providers"; -import {GLOBAL_ANONYMOUS_FEED_ADDRESS, PRIVATE_KEY, RPC_URL} from "../config"; +import {PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, PRIVATE_KEY, PUBLIC_PROVIDER} from "../config"; import {Wallet} from "@ethersproject/wallet"; import {GlobalAnonymousFeed, GlobalAnonymousFeed__factory} from "../abi"; import {BuildOrderedTree, Circuit, CircuitConfig, Prover} from "@unirep/circuits"; @@ -23,10 +23,10 @@ class EpochSealer { nextEpochEnd: number = 0; constructor() { - const provider = getDefaultProvider(RPC_URL); + const provider = getDefaultProvider(PUBLIC_PROVIDER); this.wallet = new Wallet(PRIVATE_KEY, provider); this.contract = GlobalAnonymousFeed__factory.connect( - GLOBAL_ANONYMOUS_FEED_ADDRESS, + PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS, this.wallet ); } diff --git a/packages/ui/.env b/packages/ui/.env index 058cc778..2480fd00 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -4,8 +4,8 @@ PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0x0bd11870C1EBA2e4c316fb580253abAbfBF060A0 PUBLIC_RELAYER_URL=https://relayer.kurate.apyos.dev # development -#PUBLIC_PROVIDER=http://127.0.0.1:7545 -#PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0x3bDf5135574a26EEF8648e5a51e6a9a9d87eea6E +#PUBLIC_PROVIDER=http://localhost:8545 +#PUBLIC_GLOBAL_ANONYMOUS_FEED_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 #PUBLIC_RELAYER_URL=http://localhost:3000 PUBLIC_ADAPTER=firebase diff --git a/packages/ui/src/lib/adapters/utils.ts b/packages/ui/src/lib/adapters/utils.ts index 8e9f9c85..ae040e69 100644 --- a/packages/ui/src/lib/adapters/utils.ts +++ b/packages/ui/src/lib/adapters/utils.ts @@ -7,7 +7,12 @@ import { transaction } from '$lib/stores/transaction' import type { providers } from 'ethers' type WindowWithEthereum = Window & - typeof globalThis & { ethereum: providers.ExternalProvider | any } + typeof globalThis & { + ethereum: providers.ExternalProvider & { + on: (name: string, handler: () => unknown) => void + removeListener: (name: string, handler: () => unknown) => void + } + } const windowWithEthereum = browser && (window as WindowWithEthereum) diff --git a/packages/ui/src/lib/adapters/zkitter-god-mode/index.ts b/packages/ui/src/lib/adapters/zkitter-god-mode/index.ts index 52b17236..42e493fd 100644 --- a/packages/ui/src/lib/adapters/zkitter-god-mode/index.ts +++ b/packages/ui/src/lib/adapters/zkitter-god-mode/index.ts @@ -1,7 +1,6 @@ import { getGlobalAnonymousFeed } from '$lib/services' import { sleep } from '$lib/utils' import { GroupAdapter } from '../zkitter/group-adapter' -import type { Signer } from 'ethers' import { posts } from '$lib/stores/post' import { RELAYER_URL } from '../../constants' import { ZkitterAdapter } from '../zkitter' @@ -24,16 +23,9 @@ export class ZkitterAdapterGodMode extends ZkitterAdapter { return super.start() } - async publishPost( - personaId: string, - text: string, - images: string[], - signer: Signer, - ): Promise { + async publishPost(personaId: string, text: string, images: string[]): Promise { const { Post, MessageType, PostMessageSubType } = await import('zkitter-js') - if (!signer) throw new Error('must connect with wallet first') - const zkitter = await this.getZkitterClient() // User did not join the persona yet @@ -101,8 +93,7 @@ export class ZkitterAdapterGodMode extends ZkitterAdapter { return hash } - async publishPersona(draftPersona: DraftPersona, signer: Signer): Promise { - if (!signer) throw new Error('must connect with wallet first') + async publishPersona(draftPersona: DraftPersona): Promise { if (!this.identity) throw new Error('must sign in first') if (!this.userState) throw new Error('user state is not initialized') diff --git a/packages/ui/src/lib/adapters/zkitter/group-adapter.ts b/packages/ui/src/lib/adapters/zkitter/group-adapter.ts index 99d082e8..1b776395 100644 --- a/packages/ui/src/lib/adapters/zkitter/group-adapter.ts +++ b/packages/ui/src/lib/adapters/zkitter/group-adapter.ts @@ -38,7 +38,7 @@ export class GroupAdapter extends EventEmitter2 implements GenericGroupAdapter { async sync() { if (Date.now() - this.lastSync < 60000) return const lastMemberIdCommitment = (await this.members()).pop() - // TODO: expose method in new zkitter-js release + // FIXME: let's not use any in the codebase please const lastMember = await (this.db as any) .groupMembersDB(this.groupId) .get(lastMemberIdCommitment) diff --git a/packages/ui/src/lib/adapters/zkitter/index.ts b/packages/ui/src/lib/adapters/zkitter/index.ts index 8779b234..f8db2097 100644 --- a/packages/ui/src/lib/adapters/zkitter/index.ts +++ b/packages/ui/src/lib/adapters/zkitter/index.ts @@ -8,7 +8,7 @@ import type { Adapter } from '..' import { GroupAdapter } from './group-adapter' import type { Signer } from 'ethers' import { create } from 'ipfs-http-client' -import { createIdentity, generateRLNProofForNewPersona, prover, rlnVkey } from './utils' +import { createIdentity, generateRLNProofForNewPersona, prover } from './utils' import { posts } from '$lib/stores/post' import type { GenericDBAdapterInterface, @@ -72,7 +72,7 @@ export class ZkitterAdapter implements Adapter { async start() { const { Zkitter } = await import('zkitter-js') - const contract = await getGlobalAnonymousFeed() + const contract = getGlobalAnonymousFeed() const zkitter = await Zkitter.initialize({ groups: [], topicPrefix: 'kurate_dev_5' }) @@ -124,12 +124,16 @@ export class ZkitterAdapter implements Adapter { } if (msg.type === 'CHAT') { + if (!this.identity) { + console.error('no identity') + return + } const { generateECDHKeyPairFromZKIdentity, deriveSharedSecret, decrypt, deriveChatId } = await import('zkitter-js') const chat = msg as ZKitterChat - const { zkIdentity } = this.identity! + const { zkIdentity } = this.identity const postId = chat.payload.senderSeed const msgProof = await zkitter.getProof(postId) @@ -418,8 +422,9 @@ export class ZkitterAdapter implements Adapter { }), ) } - async publishPersona(draftPersona: DraftPersona, signer: Signer): Promise { + async publishPersona(draftPersona: DraftPersona): Promise { if (!this.identity) throw new Error('must sign in first') + if (!this.userState) throw new Error('no userstate') const zkitter = await this.getZkitterClient() @@ -427,7 +432,7 @@ export class ZkitterAdapter implements Adapter { const contract = getGlobalAnonymousFeed() - await this.userState!.waitForSync() + await this.userState.waitForSync() const newPersonaId = (await contract.numOfPersonas()).toNumber() @@ -483,7 +488,7 @@ export class ZkitterAdapter implements Adapter { if (!draftPersona.picture) throw new Error('must contain a profile picture') if (!draftPersona.cover) throw new Error('must contain a cover image') - const signupProof = await this.userState!.genUserSignUpProof() + const signupProof = await this.userState.genUserSignUpProof() const repProof = await this.genRepProof(contract, 10) const resp = await fetch(`${RELAYER_URL}/create-and-join-with-rep`, { method: 'post', @@ -538,11 +543,12 @@ export class ZkitterAdapter implements Adapter { async joinPersona(personaId: string): Promise { if (!this.identity) throw new Error('must sign in first') + if (!this.userState) throw new Error('no userstate') const zkitter = await this.getZkitterClient() - await this.userState!.waitForSync() + await this.userState.waitForSync() - const signupProof = await this.userState!.genUserSignUpProof() + const signupProof = await this.userState.genUserSignUpProof() const resp = await fetch(`${RELAYER_URL}/join-persona`, { method: 'post', @@ -648,12 +654,8 @@ export class ZkitterAdapter implements Adapter { .map((_, i) => String(baseEpoch + i * (28800000 / 10))) } - async publishPost( - personaId: string, - text: string, - images: string[], - signer: Signer, - ): Promise { + async publishPost(personaId: string, text: string, images: string[]): Promise { + if (!this.identity) throw new Error('must sign in first') const { Post, MessageType, PostMessageSubType } = await import('zkitter-js') const zkitter = await this.getZkitterClient() @@ -684,7 +686,7 @@ export class ZkitterAdapter implements Adapter { const proof = await zkitter.createProof({ hash: post.hash(), - zkIdentity: this.identity!.zkIdentity, + zkIdentity: this.identity.zkIdentity, groupId: GroupAdapter.createGroupId(personaId), }) @@ -818,19 +820,20 @@ export class ZkitterAdapter implements Adapter { contract: GlobalAnonymousFeed, minRep?: number, ): Promise { - await this.userState!.waitForSync() + if (!this.userState) throw new Error('user state not initialized') + await this.userState.waitForSync() - const latestTransitionedEpoch = await this.userState!.latestTransitionedEpoch() + const latestTransitionedEpoch = await this.userState.latestTransitionedEpoch() const currentEpoch = (await contract.attesterCurrentEpoch()).toNumber() if (latestTransitionedEpoch < currentEpoch) { - const ust = await this.userState!.genUserStateTransitionProof({}) + const ust = await this.userState.genUserStateTransitionProof({}) const txHash = await this.userStateTransition(ust) const blockNumber = await this.waitForTx(txHash) - await this.userState!.waitForSync(blockNumber) + await this.userState.waitForSync(blockNumber) } - return this.userState!.genProveReputationProof({ minRep }) + return this.userState.genProveReputationProof({ minRep }) } async voteOnPost(groupId: string, postId: string, vote: '+' | '-', signer: Signer) { @@ -869,7 +872,8 @@ export class ZkitterAdapter implements Adapter { } async startChat(chat: Chat): Promise { - const { zkIdentity } = this.identity! + if (!this.identity) throw new Error('identity not initialized') + const { zkIdentity } = this.identity const zkitter = await this.getZkitterClient() const msgProof = await zkitter.getProof(chat.post.postId) @@ -893,12 +897,13 @@ export class ZkitterAdapter implements Adapter { } async publishZkitterMessage(message: Message) { - const { zkIdentity } = this.identity! + if (!this.identity) throw new Error('identity not initialized') + const { zkIdentity } = this.identity const zkitter = await this.getZkitterClient() const proof = await zkitter.createProof({ hash: message.hash(), - zkIdentity: this.identity!.zkIdentity, + zkIdentity: this.identity.zkIdentity, }) if (proof.type !== 'rln') throw new Error('invalid proof') @@ -921,12 +926,14 @@ export class ZkitterAdapter implements Adapter { console.log(data) } async sendChatMessage(chatId: string, text: string): Promise { + if (!this.identity) throw new Error('identity not initialized') const chatStore = get(chats) const chatData = chatStore.chats.get(chatId) + if (!chatData) throw new Error('chat not found') const { post: { postId }, - } = chatData! - const { zkIdentity } = this.identity! + } = chatData + const { zkIdentity } = this.identity const zkitter = await this.getZkitterClient() const msgProof = await zkitter.getProof(postId) @@ -961,7 +968,7 @@ export class ZkitterAdapter implements Adapter { chats.update((state) => { const newState = { ...state } - newState.chats.get(chatId)!.messages.push({ + newState.chats.get(chatId)?.messages.push({ timestamp: Date.now(), text, messageId: chat.hash(), @@ -971,13 +978,15 @@ export class ZkitterAdapter implements Adapter { } async subscribeToChat(chatId: string): Promise<() => unknown> { + if (!this.identity) throw new Error('identity not initialized') const chatStore = get(chats) const chatData = chatStore.chats.get(chatId) + if (!chatData) throw new Error('chat not found') const { post: { postId }, - } = chatData! + } = chatData - const { zkIdentity } = this.identity! + const { zkIdentity } = this.identity const zkitter = await this.getZkitterClient() const msgProof = await zkitter.getProof(postId) @@ -985,7 +994,7 @@ export class ZkitterAdapter implements Adapter { if (msgProof?.type !== 'rln' || !msgProof.ecdh) throw new Error('invalid proof from chat id') const { generateECDHKeyPairFromZKIdentity } = await import('zkitter-js') - const { pub, priv } = await generateECDHKeyPairFromZKIdentity(zkIdentity, postId) + const { pub } = await generateECDHKeyPairFromZKIdentity(zkIdentity, postId) const { ecdh: receiverECDH } = msgProof const chatMsgs = await zkitter.getChatMessages(chatId, undefined, undefined, { @@ -995,7 +1004,7 @@ export class ZkitterAdapter implements Adapter { }) for (let i = 0; i < chatMsgs.length; i++) { - await this.insertChat(chatMsgs[i], chatId) + this.insertChat(chatMsgs[i], chatId) } return zkitter.updateFilter({ ecdh: [pub, receiverECDH] } as never) @@ -1004,11 +1013,11 @@ export class ZkitterAdapter implements Adapter { private insertChat(chat: ZKitterChat, chatId: string) { chats.update((state) => { const newState = { ...state } - const chatStore = newState.chats.get(chatId)! + const chatStore = newState.chats.get(chatId) const chatHash = chat.hash() - if (!chatStore.messages.find(({ messageId }) => messageId === chatHash)) { - chatStore.messages.push({ + if (!chatStore?.messages.find(({ messageId }) => messageId === chatHash)) { + chatStore?.messages.push({ timestamp: chat.createdAt.getTime(), text: typeof chat.payload.content === 'undefined' ? 'encryption error' : chat.payload.content, diff --git a/packages/ui/src/lib/adapters/zkitter/utils.ts b/packages/ui/src/lib/adapters/zkitter/utils.ts index dcf51c65..d5ba5eb1 100644 --- a/packages/ui/src/lib/adapters/zkitter/utils.ts +++ b/packages/ui/src/lib/adapters/zkitter/utils.ts @@ -100,7 +100,7 @@ export async function updateActivePosts(postHashes: string[]): Promise<{ [hash: return newActives } -export async function getActivePosts(postHashes: string[]): Promise<{ [hash: string]: string }> { +export async function getActivePosts(): Promise<{ [hash: string]: string }> { return getFromLocalStorage<{ [hash: string]: string }>('kurate/actives', {}) } diff --git a/packages/ui/src/routes/persona/draft/[id]/+page.svelte b/packages/ui/src/routes/persona/draft/[id]/+page.svelte index 047b1d89..eda6d20e 100644 --- a/packages/ui/src/routes/persona/draft/[id]/+page.svelte +++ b/packages/ui/src/routes/persona/draft/[id]/+page.svelte @@ -49,6 +49,7 @@ let postToEditImages: string[] = [] let postToDelete: DraftPost | undefined = undefined let publishedPersonaId: string | undefined = undefined + let publishing = false type State = 'persona_preview' | 'edit_text' | 'edit_rep' | 'post_new' | 'publish_warning' @@ -75,10 +76,16 @@ } async function publishPersona() { - if (!$profile.signer) return + try { + publishing = true + if (!$profile.signer) throw new Error('No signer, please login') - publishedPersonaId = await adapter.publishPersona(persona, $profile.signer) - adapter.addPersonaToFavorite(publishedPersonaId) + publishedPersonaId = await adapter.publishPersona(persona, $profile.signer) + adapter.addPersonaToFavorite(publishedPersonaId) + } catch (e) { + console.error(e) + } + publishing = false } let y: number @@ -397,11 +404,18 @@ {#if $tokens.go >= TOKEN_POST_COST} -