Skip to content

Commit

Permalink
feat: local deployment steps (#413)
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtechsimetka authored May 11, 2023
1 parent 99e970a commit 054bcae
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 129 deletions.
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?**

Expand Down
33 changes: 10 additions & 23 deletions packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -54,9 +44,6 @@ const hardhatConfig: HardhatUserConfig = {
artifacts: config.paths.build.contracts
},
networks: {
hardhat: {
chainId: 1337
},
...getNetworks()
},
gasReporter: {
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 0 additions & 12 deletions packages/contracts/scripts/deploy-unirep.ts

This file was deleted.

58 changes: 36 additions & 22 deletions packages/contracts/tasks/deploy.ts
Original file line number Diff line number Diff line change
@@ -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 [email protected] 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;
})
2 changes: 1 addition & 1 deletion packages/contracts/test/GlobalAnonymousFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions packages/relayer/.env.example
Original file line number Diff line number Diff line change
@@ -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=
6 changes: 3 additions & 3 deletions packages/relayer/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -22,8 +22,8 @@ const app: FastifyPluginAsync<AppOptions> = async (
fastify.withTypeProvider<JsonSchemaToTsProvider>();

// 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();

Expand Down
6 changes: 3 additions & 3 deletions packages/relayer/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
10 changes: 5 additions & 5 deletions packages/relayer/src/routes/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -331,11 +331,11 @@ const getBodySchemaWithoutRep = () => {
const root: FastifyPluginAsyncJsonSchemaToTs = async (
fastify
): Promise<void> => {
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
);

Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions packages/relayer/src/services/epoch.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 6 additions & 1 deletion packages/ui/src/lib/adapters/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
13 changes: 2 additions & 11 deletions packages/ui/src/lib/adapters/zkitter-god-mode/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -24,16 +23,9 @@ export class ZkitterAdapterGodMode extends ZkitterAdapter {
return super.start()
}

async publishPost(
personaId: string,
text: string,
images: string[],
signer: Signer,
): Promise<string> {
async publishPost(personaId: string, text: string, images: string[]): Promise<string> {
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
Expand Down Expand Up @@ -101,8 +93,7 @@ export class ZkitterAdapterGodMode extends ZkitterAdapter {
return hash
}

async publishPersona(draftPersona: DraftPersona, signer: Signer): Promise<string> {
if (!signer) throw new Error('must connect with wallet first')
async publishPersona(draftPersona: DraftPersona): Promise<string> {
if (!this.identity) throw new Error('must sign in first')
if (!this.userState) throw new Error('user state is not initialized')

Expand Down
Loading

1 comment on commit 054bcae

@vercel
Copy link

@vercel vercel bot commented on 054bcae May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

kurate – ./

kurate-vojtechsimetka.vercel.app
kurate-git-main-vojtechsimetka.vercel.app
kurate.vercel.app

Please sign in to comment.