diff --git a/.gitmodules b/.gitmodules index 346e7c80..b8ecfd4a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "dojo-starter"] path = worlds/dojo-starter - url = https://github.com/dojoengine/dojo-starter \ No newline at end of file + url = https://github.com/dojoengine/dojo-starter +[submodule "examples/example-vite-kitchen-sink/src/onchain"] + path = examples/example-vite-kitchen-sink/src/onchain + url = https://github.com/MartianGreed/onchain-dash diff --git a/examples/example-vite-kitchen-sink/.env.dist b/examples/example-vite-kitchen-sink/.env.dist new file mode 100644 index 00000000..90ffdcd8 --- /dev/null +++ b/examples/example-vite-kitchen-sink/.env.dist @@ -0,0 +1,5 @@ +VITE_RPC_URL="http://localhost:5050" +VITE_RPC_API_KEY="" +VITE_CONTROLLER_URL="https://x.cartridge.gg/mainnet" +VITE_CONTROLLER_RPC="https://x.cartridge.gg/mainnet" +VITE_TORII_URL="http://localhost:8080" diff --git a/examples/example-vite-kitchen-sink/.eslintrc.json b/examples/example-vite-kitchen-sink/.eslintrc.json new file mode 100644 index 00000000..37224185 --- /dev/null +++ b/examples/example-vite-kitchen-sink/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/examples/example-vite-kitchen-sink/.gitignore b/examples/example-vite-kitchen-sink/.gitignore new file mode 100644 index 00000000..85851663 --- /dev/null +++ b/examples/example-vite-kitchen-sink/.gitignore @@ -0,0 +1,37 @@ +# 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 +.env diff --git a/examples/example-vite-kitchen-sink/README.md b/examples/example-vite-kitchen-sink/README.md new file mode 100644 index 00000000..e215bc4c --- /dev/null +++ b/examples/example-vite-kitchen-sink/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/examples/example-vite-kitchen-sink/components.json b/examples/example-vite-kitchen-sink/components.json new file mode 100644 index 00000000..4c43999a --- /dev/null +++ b/examples/example-vite-kitchen-sink/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/examples/example-vite-kitchen-sink/index.html b/examples/example-vite-kitchen-sink/index.html new file mode 100644 index 00000000..e0ef3be8 --- /dev/null +++ b/examples/example-vite-kitchen-sink/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/examples/example-vite-kitchen-sink/package.json b/examples/example-vite-kitchen-sink/package.json new file mode 100644 index 00000000..5509bc95 --- /dev/null +++ b/examples/example-vite-kitchen-sink/package.json @@ -0,0 +1,56 @@ +{ + "name": "sink", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@cartridge/connector": "^0.3.44", + "@dojoengine/core": "workspace:*", + "@dojoengine/sdk": "workspace:*", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.2", + "@starknet-react/chains": "^3.0.0", + "@starknet-react/core": "2.3.0", + "@t3-oss/env-core": "^0.11.1", + "@t3-oss/env-nextjs": "^0.11.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "dotenv": "^16.4.5", + "jiti": "^1.21.6", + "lucide-react": "^0.441.0", + "next": "14.2.12", + "react": "^18", + "react-dom": "^18", + "react-hook-form": "^7.53.0", + "starknet": "6.11.0", + "tailwind-merge": "^2.5.2", + "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.4", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8", + "eslint-config-next": "14.2.12", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5", + "vite": "^5.4.8", + "vite-plugin-top-level-await": "^1.4.4", + "vite-plugin-wasm": "^3.3.0", + "vite-preset-react": "^2.3.0" + } +} diff --git a/examples/example-vite-kitchen-sink/postcss.config.mjs b/examples/example-vite-kitchen-sink/postcss.config.mjs new file mode 100644 index 00000000..1a69fd2a --- /dev/null +++ b/examples/example-vite-kitchen-sink/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/examples/example-vite-kitchen-sink/src/app/favicon.ico b/examples/example-vite-kitchen-sink/src/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/examples/example-vite-kitchen-sink/src/app/favicon.ico differ diff --git a/examples/example-vite-kitchen-sink/src/app/fonts/GeistMonoVF.woff b/examples/example-vite-kitchen-sink/src/app/fonts/GeistMonoVF.woff new file mode 100644 index 00000000..f2ae185c Binary files /dev/null and b/examples/example-vite-kitchen-sink/src/app/fonts/GeistMonoVF.woff differ diff --git a/examples/example-vite-kitchen-sink/src/app/fonts/GeistVF.woff b/examples/example-vite-kitchen-sink/src/app/fonts/GeistVF.woff new file mode 100644 index 00000000..1b62daac Binary files /dev/null and b/examples/example-vite-kitchen-sink/src/app/fonts/GeistVF.woff differ diff --git a/examples/example-vite-kitchen-sink/src/app/globals.css b/examples/example-vite-kitchen-sink/src/app/globals.css new file mode 100644 index 00000000..c4f371cc --- /dev/null +++ b/examples/example-vite-kitchen-sink/src/app/globals.css @@ -0,0 +1,85 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + color: hsl(var(--foreground)); + background: hsl(var(--background)); +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 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; + --font-body: var(--font-geist-sans); + --font-heading: var(--font-geist-mono); + } + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --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; + font-family: var(--font-geist-sans); + } + h1, h2, h3, h4, h5, h6, h7 { + font-family: var(--font-geist-sans); + } +} diff --git a/examples/example-vite-kitchen-sink/src/app/layout.tsx b/examples/example-vite-kitchen-sink/src/app/layout.tsx new file mode 100644 index 00000000..06bf6c4c --- /dev/null +++ b/examples/example-vite-kitchen-sink/src/app/layout.tsx @@ -0,0 +1,25 @@ +import { TooltipProvider } from "@/components/ui/tooltip"; + +import Sidebar from "@/components/sidebar"; +import Header from "@/components/header"; +import StarknetProvider from "@/components/starknet-provider"; +import DojoProvider from "@/dojo/provider"; + + +export default function RootLayout({ children }) { + return ( + + + +
+ +
+
+ {children} +
+
+
+
+
+ ); +} diff --git a/examples/example-vite-kitchen-sink/src/app/page.tsx b/examples/example-vite-kitchen-sink/src/app/page.tsx new file mode 100644 index 00000000..453a90c2 --- /dev/null +++ b/examples/example-vite-kitchen-sink/src/app/page.tsx @@ -0,0 +1,86 @@ +import { useDojoDb } from "@/dojo/provider" +import { useEffect, useState } from "react" +import { OnchainDashSchemaType } from "@/dojo/models" +import { SDK } from "@dojoengine/sdk" +import { Subscription } from "@dojoengine/torii-client" +import GlobalCounter from "@/components/global-counter" +import CallerCounter from "@/components/caller-counter" +import Chat from "@/components/chat" + +export default function Home() { + const [subscription, setSubscription] = useState() + + const db = useDojoDb(); + useEffect(() => { + async function getEntities(db: SDK) { + const sub = await db.subscribeEntityQuery( + { + onchain_dash: { + GlobalCounter: { + $: {}, + }, + CallerCounter: { + $: {} + } + }, + }, + (response) => { + if (response.error) { + console.error( + "Error querying todos and goals:", + response.error + ); + return; + } + if (response.data) { + console.log(response.data) + } + } + ); + setSubscription(sub) + } + + if (db && !subscription) { + getEntities(db) + } + + return () => { + if (subscription) { + if (subscription.free) { + subscription.free(); + } + } + } + }, [db, subscription]) + + + + return ( +
+
+
+
+ + Settings + +
+
+
+ + +
+ + Stats + +
+ Some stats about whats happening +
+
+
+
+ +
+ ); +} diff --git a/examples/example-vite-kitchen-sink/src/components/caller-counter.tsx b/examples/example-vite-kitchen-sink/src/components/caller-counter.tsx new file mode 100644 index 00000000..51eb1b24 --- /dev/null +++ b/examples/example-vite-kitchen-sink/src/components/caller-counter.tsx @@ -0,0 +1,92 @@ +import { useCallback, useEffect, useState } from "react"; +import { Button } from "./ui/button"; +import { useAccount, useContractWrite } from "@starknet-react/core"; +import { useDojoDb } from "@/dojo/provider"; +import { ensureStarkFelt } from "@/lib/utils"; +import { SDK } from "@dojoengine/sdk"; +import { OnchainDashSchemaType } from "@/dojo/models"; +import { Subscription } from "@dojoengine/torii-wasm"; + +export default function CallerCounter() { + const [count, setCount] = useState(0); + const [sub, setSub] = useState(null); + const { address } = useAccount(); + const { write: incrementCallerCounter } = useContractWrite({ + calls: [{ + contractAddress: "0x00a6cf5a4945bd1ebf38fac5282eaf3cf3b615186987f5cd16b4b9eef9cc3ab2", + entrypoint: "increment_caller_counter", + calldata: [] + }] + }); + + const handleCallerClick = useCallback(async () => { + incrementCallerCounter(); + }, [incrementCallerCounter]); + + const db = useDojoDb(); + useEffect(() => { + async function getEntity(db: SDK, address: string) { + const entity = await db.getEntities({ + onchain_dash: { + CallerCounter: { $: { where: { caller: { $eq: ensureStarkFelt(address) } } } } + } + }, ({ data, error }) => { }); + const counter = entity.pop(); + if (!counter) { + return 0; + } + const count = parseInt(counter.models.onchain_dash.CallerCounter.counter, 16); + return count; + } + if (address && db) { + getEntity(db, address).then(setCount).catch(console.error) + } + }, [address, db]) + + useEffect(() => { + async function subscribeToEntityUpdates(db: SDK, address: string) { + const sub = await db.subscribeEntityQuery({ + onchain_dash: { CallerCounter: { $: { where: { caller: { $eq: ensureStarkFelt(address) } } } } } + }, ({ data, error }) => { + if (data) { + const entity = data.pop(); + if (!entity) { + return; + } + if (entity.models.onchain_dash?.CallerCounter?.counter === undefined) { + return + } + const count = parseInt(entity.models.onchain_dash?.CallerCounter?.counter, 16); + setCount(count); + return; + } + if (error) { + throw error; + } + }); + setSub(sub); + } + if (address && db && sub === null) { + subscribeToEntityUpdates(db, address).then(() => { }).catch(console.error) + } + return () => { + if (sub) { + sub.free(); + } + }; + }, [address, db, sub]); + return ( +
+ + Per wallet counter + +
+ Count : {count} +
+
+ +
+
+ + ); +} diff --git a/examples/example-vite-kitchen-sink/src/components/chat.tsx b/examples/example-vite-kitchen-sink/src/components/chat.tsx new file mode 100644 index 00000000..a0ad4d42 --- /dev/null +++ b/examples/example-vite-kitchen-sink/src/components/chat.tsx @@ -0,0 +1,92 @@ +import { CornerDownLeft } from "lucide-react"; +import { Badge } from "./ui/badge"; +import { Button } from "./ui/button"; +import { Label } from "./ui/label"; +import { Textarea } from "./ui/textarea"; +import { useCallback } from "react"; +import { useForm } from "react-hook-form"; +import { useDojoDb } from "@/dojo/provider"; +import { useAccount } from "@starknet-react/core"; +import { toValidAscii } from "@/lib/utils"; +import { TypedData } from "starknet"; + +function generateTypedData(identity: string, content: string, timestamp: number = Date.now()) { + return { + types: { + StarknetDomain: [ + { name: "name", type: "shortstring" }, + { name: "version", type: "shortstring" }, + { name: "revision", type: "shortstring" }, + ], + "onchain_dash-Message": [ + { name: "identity", type: "ContractAddress" }, + { name: "content", type: "string" }, + { name: "timestamp", type: "felt" }, + ], + }, + primaryType: "onchain_dash-Message", + domain: { + name: "OnChainDash", + version: "1", + chainId: "1", + revision: "1", + }, + message: { + identity, + content, + timestamp, + }, + }; +} + + +export default function Chat() { + const { register, handleSubmit } = useForm(); + const { account } = useAccount(); + + const db = useDojoDb(); + const publish = useCallback(async (data) => { + if (!account) return; + + const asciiMessage = toValidAscii(data.message); + const msg = generateTypedData(account?.address, asciiMessage) + try { + const signature = await account.signMessage(msg as TypedData); + + console.log(signature) + } catch (error) { + console.error("failed to sign message:", error); + } + // await db.client.publishMessage(JSON.stringify(msg), [signature.r, signature.s]); + // console.log("publish", data) + }, [db, account]); + + return ( +
publish(data))}> + + Output + +
+
+ +