diff --git a/.env.yarn.example b/.env.yarn.example new file mode 100644 index 00000000..536d991d --- /dev/null +++ b/.env.yarn.example @@ -0,0 +1,4 @@ +REDIS_URL= +SIMPLEHASH_API_KEY= + +NEXT_PUBLIC_API_URL=http://localhost:3001 diff --git a/.gitignore b/.gitignore index 870eb6a5..f33c6406 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +dist + +**/node_modules .yarn/* !.yarn/patches !.yarn/plugins @@ -5,9 +8,45 @@ !.yarn/sdks !.yarn/versions -# Swap the comments on the following lines if you wish to use zero-installs -# In that case, don't forget to run `yarn config set enableGlobalCache false`! -# Documentation here: https://yarnpkg.com/features/caching#zero-installs +# expo +**/.expo/* + +# next.js +**/.next/* +/out/ + +# tamagui +**/.tamagui/* + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +# os +.DS_Store +THUMBS_DB +thumbs.db + +ios/ +android/ -#!.yarn/cache -.pnp.* +# env +.env +.env.yarn \ No newline at end of file diff --git a/apps/next/.eslintrc.json b/apps/next/.eslintrc.json new file mode 100644 index 00000000..37224185 --- /dev/null +++ b/apps/next/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/apps/next/.gitignore b/apps/next/.gitignore new file mode 100644 index 00000000..fd3dbb57 --- /dev/null +++ b/apps/next/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/next/README.md b/apps/next/README.md new file mode 100644 index 00000000..e215bc4c --- /dev/null +++ b/apps/next/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/next/app/api/merkle-tree/route.ts b/apps/next/app/api/merkle-tree/route.ts new file mode 100644 index 00000000..d2ef8bb2 --- /dev/null +++ b/apps/next/app/api/merkle-tree/route.ts @@ -0,0 +1,66 @@ +import { NextRequest, NextResponse } from "next/server"; +import Redis from "ioredis"; +import { buildMimc7 as buildMimc } from "circomlibjs"; +import { MerkleTreeMiMC, MiMC7 } from "@/lib/merkle-tree"; +import { fetchTopOwners, Owner } from "@/lib/simplehash"; + +const redis = new Redis(process.env.REDIS_URL as string); + +export async function GET(request: NextRequest) { + const tree = await fetchTree(); + + return NextResponse.json(tree); +} + +async function fetchTree() { + const data = await redis.get("anon:tree"); + if (data) { + return JSON.parse(data); + } + + const mimc = await buildMimc(); + const merkleTree = new MerkleTreeMiMC(11, mimc); + + const owners = await fetchOwners(); + for (const owner of owners) { + const commitment = MiMC7( + mimc, + owner.owner_address.toLowerCase().replace("0x", ""), + BigInt(owner.quantity_string).toString(16).replace("0x", ""), + ); + merkleTree.insert(commitment); + } + + const root = `0x${merkleTree.root()}`; + + const elements = owners.map((owner, index) => { + return { + path: merkleTree + .proof(index) + .pathElements.map((p) => `0x${p}` as `0x${string}`), + address: owner.owner_address.toLowerCase(), + balance: owner.quantity_string, + }; + }); + + const tree = { + root, + elements, + }; + + await redis.set("anon:tree", JSON.stringify(tree), "EX", 60 * 5); + + return tree; +} + +async function fetchOwners(): Promise> { + const data = await redis.get("anon:owners"); + if (data) { + return JSON.parse(data); + } + + const owners = await fetchTopOwners(); + await redis.set("anon:owners", JSON.stringify(owners), "EX", 60 * 5); + + return owners; +} diff --git a/apps/next/app/api/post/route.ts b/apps/next/app/api/post/route.ts new file mode 100644 index 00000000..3bd307df --- /dev/null +++ b/apps/next/app/api/post/route.ts @@ -0,0 +1,54 @@ +import { verifyProof } from "@/lib/proof"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + const body: { + proof: number[]; + publicInputs: number[][]; + } = await request.json(); + + let isValid = false; + try { + isValid = await verifyProof(body.proof, body.publicInputs); + } catch (e) { + console.error(e); + } + + if (!isValid) { + return NextResponse.json({ + success: false, + }); + } + + return NextResponse.json({ + success: true, + }); +} + +function extractData(data: number[][]): { + timestamp: number; + root: `0x${string}`; + text: string; +} { + const timestampBuffer = Buffer.from(data[0]); + let timestamp = 0; + for (let i = 0; i < timestampBuffer.length; i++) { + timestamp = timestamp * 256 + timestampBuffer[i]; + } + + const root = `0x${Buffer.from(data[1]).toString("hex")}`; + + const messageArrays = data.slice(2, 2 + 16); + // @ts-ignore + const messageBytes = [].concat(...messageArrays); + const decoder = new TextDecoder("utf-8"); + const message = decoder + .decode(Uint8Array.from(messageBytes)) + .replace(/\0/g, ""); + + return { + timestamp, + root: root as `0x${string}`, + text: message, + }; +} diff --git a/apps/next/app/favicon.ico b/apps/next/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/apps/next/app/favicon.ico differ diff --git a/apps/next/app/fonts/SF-Mono-Bold.otf b/apps/next/app/fonts/SF-Mono-Bold.otf new file mode 100644 index 00000000..088f548f Binary files /dev/null and b/apps/next/app/fonts/SF-Mono-Bold.otf differ diff --git a/apps/next/app/fonts/SF-Mono-Semibold.otf b/apps/next/app/fonts/SF-Mono-Semibold.otf new file mode 100644 index 00000000..ac9fb963 Binary files /dev/null and b/apps/next/app/fonts/SF-Mono-Semibold.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Black.otf b/apps/next/app/fonts/SF-Pro-Rounded-Black.otf new file mode 100755 index 00000000..e622f8a2 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Black.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Bold.otf b/apps/next/app/fonts/SF-Pro-Rounded-Bold.otf new file mode 100755 index 00000000..558f4ec6 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Bold.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Heavy.otf b/apps/next/app/fonts/SF-Pro-Rounded-Heavy.otf new file mode 100755 index 00000000..cc7b6700 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Heavy.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Light.otf b/apps/next/app/fonts/SF-Pro-Rounded-Light.otf new file mode 100755 index 00000000..c0b8f273 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Light.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Medium.otf b/apps/next/app/fonts/SF-Pro-Rounded-Medium.otf new file mode 100755 index 00000000..857a99f8 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Medium.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Regular.otf b/apps/next/app/fonts/SF-Pro-Rounded-Regular.otf new file mode 100755 index 00000000..78abc122 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Regular.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Semibold.otf b/apps/next/app/fonts/SF-Pro-Rounded-Semibold.otf new file mode 100755 index 00000000..c56ef8d0 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Semibold.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Thin.otf b/apps/next/app/fonts/SF-Pro-Rounded-Thin.otf new file mode 100755 index 00000000..c27f165f Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Thin.otf differ diff --git a/apps/next/app/fonts/SF-Pro-Rounded-Ultralight.otf b/apps/next/app/fonts/SF-Pro-Rounded-Ultralight.otf new file mode 100755 index 00000000..16339f44 Binary files /dev/null and b/apps/next/app/fonts/SF-Pro-Rounded-Ultralight.otf differ diff --git a/apps/next/app/globals.css b/apps/next/app/globals.css new file mode 100644 index 00000000..1dcb0fc6 --- /dev/null +++ b/apps/next/app/globals.css @@ -0,0 +1,78 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/apps/next/app/layout.tsx b/apps/next/app/layout.tsx new file mode 100644 index 00000000..f9ce194b --- /dev/null +++ b/apps/next/app/layout.tsx @@ -0,0 +1,72 @@ +import type { Metadata } from "next"; +import localFont from "next/font/local"; +import "./globals.css"; +import { Providers } from "@/components/providers"; + +const sfProRounded = localFont({ + src: [ + { + path: "./fonts/SF-Pro-Rounded-Ultralight.otf", + weight: "100", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Thin.otf", + weight: "200", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Light.otf", + weight: "300", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Regular.otf", + weight: "400", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Medium.otf", + weight: "500", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Semibold.otf", + weight: "600", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Bold.otf", + weight: "700", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Heavy.otf", + weight: "800", + style: "normal", + }, + { + path: "./fonts/SF-Pro-Rounded-Black.otf", + weight: "900", + style: "normal", + }, + ], +}); +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/apps/next/app/page.tsx b/apps/next/app/page.tsx new file mode 100644 index 00000000..d13fe7b2 --- /dev/null +++ b/apps/next/app/page.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import { ConnectButton } from "@/components/connect-button"; +import { createProof } from "@/lib/proof"; +import { useAccount } from "wagmi"; +import { Textarea } from "@/components/ui/textarea"; + +export default function Home() { + return ( +
+
+
$ANON
+ +
+ +
+ ); +} + +function CreatePost() { + const [post, setPost] = useState(""); + const { address } = useAccount(); + + const handleCreatePost = async () => { + if (!address) return; + + const proof = await createProof(address, post); + if (!proof) { + return; + } + + await fetch(`${process.env.NEXT_PUBLIC_API_URL}/post`, { + method: "POST", + body: JSON.stringify({ + proof: Array.from(proof.proof), + publicInputs: proof.publicInputs.map((i) => Array.from(i)), + }), + headers: { + "Content-Type": "application/json", + }, + }); + }; + + return ( +
+