diff --git a/e2e/packages/contracts/worlds.json b/e2e/packages/contracts/worlds.json index 436a70cafe..6a4cd8b375 100644 --- a/e2e/packages/contracts/worlds.json +++ b/e2e/packages/contracts/worlds.json @@ -1,5 +1,5 @@ { "31337": { - "address": "0x7de562d80d6c8672aa32654af67884f411976593" + "address": "0x781ad201b31f188714172cbf1337baa0f1334156" } } \ No newline at end of file diff --git a/e2e/packages/sync-test/data/encodeTestData.ts b/e2e/packages/sync-test/data/encodeTestData.ts index b3f885cba7..2b52075445 100644 --- a/e2e/packages/sync-test/data/encodeTestData.ts +++ b/e2e/packages/sync-test/data/encodeTestData.ts @@ -16,6 +16,7 @@ import config from "../../contracts/mud.config"; export function encodeTestData(testData: Data) { return mapObject(testData, (records, table) => { if (!records) return undefined; + const tableConfig = config.tables[table]; return records.map((record) => { const keySchema = getSchemaTypes(getKeySchema(tableConfig)); @@ -23,7 +24,6 @@ export function encodeTestData(testData: Data) { const key = encodeKey(keySchema, record.key); const valueArgs = encodeValueArgs(valueSchema, record.value); const fieldLayout = valueSchemaToFieldLayoutHex(valueSchema); - return { key, ...valueArgs, diff --git a/e2e/packages/sync-test/package.json b/e2e/packages/sync-test/package.json index 71a17ea3a1..bea743df9a 100644 --- a/e2e/packages/sync-test/package.json +++ b/e2e/packages/sync-test/package.json @@ -34,6 +34,6 @@ "viem": "2.9.20", "vite": "^4.2.1", "vitest": "0.34.6", - "zod": "^3.22.2" + "zod": "^3.23.8" } } diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index f69e72be81..d3cdd0be5f 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -67,7 +67,7 @@ importers: version: 1.7.0 viem: specifier: 2.9.20 - version: 2.9.20(typescript@5.4.2)(zod@3.22.2) + version: 2.9.20(typescript@5.4.2)(zod@3.23.8) devDependencies: rimraf: specifier: ^3.0.2 @@ -164,7 +164,7 @@ importers: version: 0.0.6 abitype: specifier: 1.0.0 - version: 1.0.0(typescript@5.4.2)(zod@3.22.2) + version: 1.0.0(typescript@5.4.2)(zod@3.23.8) chalk: specifier: ^5.2.0 version: 5.2.0 @@ -188,7 +188,7 @@ importers: version: 5.4.2 viem: specifier: 2.9.20 - version: 2.9.20(typescript@5.4.2)(zod@3.22.2) + version: 2.9.20(typescript@5.4.2)(zod@3.23.8) vite: specifier: ^4.2.1 version: 4.3.5(@types/node@20.1.3) @@ -196,8 +196,8 @@ importers: specifier: 0.34.6 version: 0.34.6(happy-dom@12.10.3) zod: - specifier: ^3.22.2 - version: 3.22.2 + specifier: ^3.23.8 + version: 3.23.8 packages/test-data: devDependencies: @@ -227,7 +227,7 @@ importers: version: 5.4.2 viem: specifier: 2.9.20 - version: 2.9.20(typescript@5.4.2)(zod@3.22.2) + version: 2.9.20(typescript@5.4.2)(zod@3.23.8) packages: @@ -1306,8 +1306,8 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - zod@3.22.2: - resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} snapshots: @@ -1584,10 +1584,10 @@ snapshots: loupe: 2.3.6 pretty-format: 29.7.0 - abitype@1.0.0(typescript@5.4.2)(zod@3.22.2): + abitype@1.0.0(typescript@5.4.2)(zod@3.23.8): optionalDependencies: typescript: 5.4.2 - zod: 3.22.2 + zod: 3.23.8 abort-controller-x@0.4.1: {} @@ -2084,14 +2084,14 @@ snapshots: ufo@1.3.2: {} - viem@2.9.20(typescript@5.4.2)(zod@3.22.2): + viem@2.9.20(typescript@5.4.2)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.10.0 '@noble/curves': 1.2.0 '@noble/hashes': 1.3.2 '@scure/bip32': 1.3.2 '@scure/bip39': 1.2.1 - abitype: 1.0.0(typescript@5.4.2)(zod@3.22.2) + abitype: 1.0.0(typescript@5.4.2)(zod@3.23.8) isows: 1.0.3(ws@8.13.0) ws: 8.13.0 optionalDependencies: @@ -2206,4 +2206,4 @@ snapshots: yocto-queue@1.0.0: {} - zod@3.22.2: {} + zod@3.23.8: {} diff --git a/package.json b/package.json index 4235b8fbe1..0066b9ec11 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/latticexyz/mud.git" }, "scripts": { - "all-build": "for dir in packages/store packages/world packages/world-modules packages/cli test/mock-game-contracts e2e/packages/contracts examples/*/packages/contracts templates/*/packages/contracts; do (cd \"$dir\" && pwd && pnpm build); done", + "all-build": "for dir in packages/store packages/world packages/world-modules packages/worlds-explorer packages/cli test/mock-game-contracts e2e/packages/contracts examples/*/packages/contracts templates/*/packages/contracts; do (cd \"$dir\" && pwd && pnpm build); done", "all-install": "for dir in . docs e2e examples/* templates/*; do (cd \"$dir\" && pwd && pnpm install); done", "bench": "pnpm run --recursive bench", "build": "turbo run build", diff --git a/packages/cli/package.json b/packages/cli/package.json index 03df1ce454..03d1106a4a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -68,7 +68,7 @@ "typescript": "5.4.2", "viem": "2.9.20", "yargs": "^17.7.1", - "zod": "^3.22.2", + "zod": "^3.23.8", "zod-validation-error": "^1.3.0" }, "devDependencies": { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 3112f99422..9a61dc3be8 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1 +1,7 @@ // nothing to export + +import { functionSignatureToAbiItem } from "./utils/functionSignatureToAbiItem"; +import { getWorldAbi } from "./utils/getWorldAbi"; +import { getWorldDeploy } from "./deploy/getWorldDeploy"; + +export { functionSignatureToAbiItem, getWorldAbi, getWorldDeploy }; diff --git a/packages/cli/src/utils/functionSignatureToAbiItem.ts b/packages/cli/src/utils/functionSignatureToAbiItem.ts new file mode 100644 index 0000000000..e123aeecee --- /dev/null +++ b/packages/cli/src/utils/functionSignatureToAbiItem.ts @@ -0,0 +1,6 @@ +import { AbiItem, parseAbiItem } from "viem"; + +export function functionSignatureToAbiItem(functionSignature: string): AbiItem { + const formattedSignature = `function ${functionSignature}`; + return parseAbiItem(formattedSignature); +} diff --git a/packages/cli/src/utils/getWorldAbi.ts b/packages/cli/src/utils/getWorldAbi.ts new file mode 100644 index 0000000000..99dc17cd86 --- /dev/null +++ b/packages/cli/src/utils/getWorldAbi.ts @@ -0,0 +1,20 @@ +import { Abi } from "abitype"; +import { getSystems } from "../deploy/getSystems"; +import { functionSignatureToAbiItem } from "./functionSignatureToAbiItem"; +import { Client } from "viem"; +import { WorldDeploy } from "../deploy/common"; + +export async function getWorldAbi({ + client, + worldDeploy, +}: { + readonly client: Client; + readonly worldDeploy: WorldDeploy; +}): Promise { + const systems = await getSystems({ client, worldDeploy }); + const worldAbi = systems.flatMap((system) => + system.functions.map((func) => functionSignatureToAbiItem(func.signature)), + ); + + return worldAbi; +} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index 3c4776d5a3..192b7751b2 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -21,7 +21,7 @@ export default defineConfig({ entry: ["src/index.ts", "src/mud.ts"], target: "esnext", format: ["esm"], - dts: !process.env.TSUP_SKIP_DTS, + dts: false, // !process.env.TSUP_SKIP_DTS, sourcemap: true, clean: true, minify: true, diff --git a/packages/faucet/package.json b/packages/faucet/package.json index df157cda6f..8ca8b9327b 100644 --- a/packages/faucet/package.json +++ b/packages/faucet/package.json @@ -46,7 +46,7 @@ "dotenv": "^16.0.3", "fastify": "^4.21.0", "viem": "2.9.20", - "zod": "^3.22.2" + "zod": "^3.23.8" }, "devDependencies": { "@types/debug": "^4.1.7", diff --git a/packages/store-indexer/package.json b/packages/store-indexer/package.json index b685affebc..8c61eb5bc5 100644 --- a/packages/store-indexer/package.json +++ b/packages/store-indexer/package.json @@ -73,7 +73,7 @@ "superjson": "^1.12.4", "trpc-koa-adapter": "^1.1.3", "viem": "2.9.20", - "zod": "^3.22.2" + "zod": "^3.23.8" }, "devDependencies": { "@types/accepts": "^1.3.7", diff --git a/packages/store-sync/package.json b/packages/store-sync/package.json index 17a41ceb44..2499c0ad87 100644 --- a/packages/store-sync/package.json +++ b/packages/store-sync/package.json @@ -83,7 +83,7 @@ "sql.js": "^1.8.0", "superjson": "^1.12.4", "viem": "2.9.20", - "zod": "^3.22.2", + "zod": "^3.23.8", "zustand": "^4.3.7" }, "devDependencies": { diff --git a/packages/world-modules/package.json b/packages/world-modules/package.json index e8b271611c..d87ea72f44 100644 --- a/packages/world-modules/package.json +++ b/packages/world-modules/package.json @@ -47,7 +47,7 @@ "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", "@latticexyz/world": "workspace:*", - "zod": "^3.22.2" + "zod": "^3.23.8" }, "devDependencies": { "@latticexyz/abi-ts": "workspace:*", diff --git a/packages/worlds-explorer/.eslintrc.json b/packages/worlds-explorer/.eslintrc.json new file mode 100644 index 0000000000..bffb357a71 --- /dev/null +++ b/packages/worlds-explorer/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/packages/worlds-explorer/.gitignore b/packages/worlds-explorer/.gitignore new file mode 100644 index 0000000000..fd3dbb571a --- /dev/null +++ b/packages/worlds-explorer/.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/packages/worlds-explorer/.prettierrc b/packages/worlds-explorer/.prettierrc new file mode 100644 index 0000000000..62c0e69824 --- /dev/null +++ b/packages/worlds-explorer/.prettierrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + "prettier-plugin-tailwindcss", + "@trivago/prettier-plugin-sort-imports" + ], + "importOrder": ["^[react]", "^@(?!/)", "^@/", "^[./]"], + "importOrderSortSpecifiers": true +} diff --git a/packages/worlds-explorer/README.md b/packages/worlds-explorer/README.md new file mode 100644 index 0000000000..c4033664f8 --- /dev/null +++ b/packages/worlds-explorer/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/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/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## 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/deployment) for more details. diff --git a/packages/worlds-explorer/components.json b/packages/worlds-explorer/components.json new file mode 100644 index 0000000000..7559f63f10 --- /dev/null +++ b/packages/worlds-explorer/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/packages/worlds-explorer/next.config.mjs b/packages/worlds-explorer/next.config.mjs new file mode 100644 index 0000000000..d4347dfc81 --- /dev/null +++ b/packages/worlds-explorer/next.config.mjs @@ -0,0 +1,10 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: "standalone", + webpack: (config) => { + config.externals.push("pino-pretty", "lokijs", "encoding"); + return config; + }, +}; + +export default nextConfig; diff --git a/packages/worlds-explorer/package.json b/packages/worlds-explorer/package.json new file mode 100644 index 0000000000..7acdb479cf --- /dev/null +++ b/packages/worlds-explorer/package.json @@ -0,0 +1,69 @@ +{ + "name": "@latticexyz/worlds-explorer", + "version": "0.1.0", + "private": true, + "bin": { + "worlds-explorer": ".next/standalone/packages/worlds-explorer/server.js" + }, + "scripts": { + "build": "next build && cp -r .next/static .next/standalone/packages/worlds-explorer/.next && pnpm link .", + "dev": "next dev", + "lint": "next lint", + "start": "node .next/standalone/packages/worlds-explorer/server.js" + }, + "dependencies": { + "@hookform/resolvers": "^3.9.0", + "@latticexyz/cli": "workspace:*", + "@latticexyz/common": "workspace:*", + "@latticexyz/protocol-parser": "workspace:*", + "@latticexyz/schema-type": "workspace:*", + "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/themes": "^3.0.5", + "@tanstack/react-query": "^5.51.3", + "@tanstack/react-table": "^8.19.3", + "@uidotdev/usehooks": "^2.4.1", + "@wagmi/core": "^2.12.1", + "better-sqlite3": "^8.6.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lodash": "^4.17.21", + "lucide-react": "^0.408.0", + "next": "14.2.3", + "next-themes": "^0.3.0", + "query-string": "^9.1.0", + "react": "^18", + "react-contenteditable": "^3.3.7", + "react-dom": "^18", + "react-highlight-words": "^0.20.0", + "react-hook-form": "^7.52.1", + "sonner": "^1.5.0", + "tailwind-merge": "^1.12.0", + "tailwindcss-animate": "^1.0.7", + "viem": "2.9.20", + "wagmi": "^2.11.1", + "zod": "^3.23.8", + "zustand": "^4.3.7" + }, + "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "^4.3.0", + "@types/better-sqlite3": "^7.6.4", + "@types/lodash": "^4.17.7", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/react-highlight-words": "^0.20.0", + "eslint": "^8", + "eslint-config-next": "14.2.3", + "postcss": "^8", + "prettier": "3.2.5", + "prettier-plugin-tailwindcss": "^0.6.5", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +} diff --git a/packages/worlds-explorer/postcss.config.mjs b/packages/worlds-explorer/postcss.config.mjs new file mode 100644 index 0000000000..1a69fd2a45 --- /dev/null +++ b/packages/worlds-explorer/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/packages/worlds-explorer/public/next.svg b/packages/worlds-explorer/public/next.svg new file mode 100644 index 0000000000..5174b28c56 --- /dev/null +++ b/packages/worlds-explorer/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/worlds-explorer/public/vercel.svg b/packages/worlds-explorer/public/vercel.svg new file mode 100644 index 0000000000..d2f8422273 --- /dev/null +++ b/packages/worlds-explorer/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/worlds-explorer/src/app/(home)/DataExplorer.tsx b/packages/worlds-explorer/src/app/(home)/DataExplorer.tsx new file mode 100644 index 0000000000..0ce98528f1 --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/DataExplorer.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { SQLEditor } from "./SQLEditor"; +import { TableSelector } from "./TableSelector"; +import { TablesViewer } from "./TablesViewer"; + +export function DataExplorer() { + const searchParams = useSearchParams(); + const [query, setQuery] = useState(); + const { data: tables, isLoading: tablesLoading } = useQuery({ + queryKey: ["tables"], + queryFn: async () => { + const response = await fetch("/api/tables"); + return response.json(); + }, + select: (data) => data.tables.map((table: { name: string }) => table.name), + refetchInterval: 15000, + }); + const selectedTable = + searchParams.get("table") || (tables?.length > 0 ? tables[0] : null); + + return ( + <> + + + + + ); +} diff --git a/packages/worlds-explorer/src/app/(home)/EditableTableCell.tsx b/packages/worlds-explorer/src/app/(home)/EditableTableCell.tsx new file mode 100644 index 0000000000..1cb510e24e --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/EditableTableCell.tsx @@ -0,0 +1,137 @@ +import { Loader } from "lucide-react"; +import { toast } from "sonner"; +import { parseEventLogs } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { useWriteContract } from "wagmi"; +import { ChangeEvent, useState } from "react"; +import { encodeField } from "@latticexyz/protocol-parser/internal"; +import { waitForTransactionReceipt } from "@wagmi/core"; +import { Checkbox } from "@/components/ui/checkbox"; +import { ACCOUNT_PRIVATE_KEYS } from "@/consts"; +import { useWorldAddress } from "@/hooks/useWorldAddress"; +import { useStore } from "@/store"; +import { wagmiConfig } from "../_providers"; +import { TableConfig } from "../api/table/route"; +import { abi } from "./abi"; +import { getFieldIdx } from "./utils/getFieldIdx"; + +type Props = { + name: string; + value: string; + keyTuple: [string]; + config: TableConfig; +}; + +export function EditableTableCell({ + name, + config, + keyTuple, + value: defaultValue, +}: Props) { + const { account } = useStore(); + const { writeContractAsync } = useWriteContract(); + const worldAddress = useWorldAddress(); + + const [loading, setLoading] = useState(false); + const [prevValue, setPrevValue] = useState(defaultValue); + const [value, setValue] = useState(defaultValue); + + const tableId = config?.table_id; + const fieldType = config?.value_schema[name]; + + const handleSubmit = async (newValue: unknown) => { + const valueToSet = newValue === undefined ? value : newValue; + if (newValue !== undefined) { + setValue(newValue); + } + + if (valueToSet === prevValue) { + return; + } + + setPrevValue(valueToSet); + setLoading(true); + + const toastId = toast.loading("Transaction submitted"); + try { + const fieldIdx = getFieldIdx(config?.value_schema, name); + const txHash = await writeContractAsync({ + account: privateKeyToAccount(ACCOUNT_PRIVATE_KEYS[account]), + abi, + address: worldAddress, + functionName: "setField", + args: [tableId, keyTuple, fieldIdx, encodeField(fieldType, valueToSet)], + }); + + const transactionReceipt = await waitForTransactionReceipt(wagmiConfig, { + hash: txHash, + pollingInterval: 100, + }); + + const logs = parseEventLogs({ + abi: abi, + logs: transactionReceipt.logs, + }); + console.log("logs:", logs); + + toast.success(`Transaction successful with hash: ${txHash}`, { + id: toastId, + }); + + console.log("result:", txHash, transactionReceipt); + } catch (error) { + console.log("error:", error); + + toast.error("Uh oh! Something went wrong.", { + id: toastId, + }); + } + { + setLoading(false); + } + }; + + if (fieldType === "bool") { + return ( + { + const newValue = value === "1" ? "0" : "1"; + handleSubmit(newValue); + }} + /> + ); + } + + if (loading) { + return ( +
+ {String(value)} +
+ ); + } + + return ( +
{ + handleSubmit(value); + }} + > + ) => { + const value = evt.target.value; + setValue(value); + }} + onFocus={() => { + setPrevValue(value); + }} + onBlur={() => { + handleSubmit(value); + }} + defaultValue={String(value)} + /> +
+ ); +} diff --git a/packages/worlds-explorer/src/app/(home)/SQLEditor.tsx b/packages/worlds-explorer/src/app/(home)/SQLEditor.tsx new file mode 100644 index 0000000000..90797db33f --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/SQLEditor.tsx @@ -0,0 +1,44 @@ +import { FormEvent, useEffect, useState } from "react"; + +export function SQLEditor({ + table, + setQuery, + tablesLoading, +}: { + table: string | undefined; + setQuery: React.Dispatch>; + tablesLoading: boolean; +}) { + const [deferredQuery, setDeferredQuery] = useState(""); + + const submitQuery = (evt: FormEvent) => { + evt.preventDefault(); + setQuery(deferredQuery); + }; + + useEffect(() => { + if (table) { + const initialQuery = `SELECT * FROM '${table}' LIMIT 30`; + setQuery(initialQuery); + setDeferredQuery(initialQuery); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [table, tablesLoading]); + + return ( +
+ {/* + setDeferredQuery(evt.target.value)} + > + + + + + */} +
+ ); +} diff --git a/packages/worlds-explorer/src/app/(home)/TableSelector.tsx b/packages/worlds-explorer/src/app/(home)/TableSelector.tsx new file mode 100644 index 0000000000..36a67c5204 --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/TableSelector.tsx @@ -0,0 +1,53 @@ +import { Lock } from "lucide-react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { NON_EDITABLE_TABLES } from "@/consts"; +import { useWorldAddress } from "@/hooks/useWorldAddress"; + +export function TableSelector({ + value, + options, +}: { + value: string | undefined; + options: string[]; +}) { + const worldAddress = useWorldAddress(); + return ( +
+ +
+ ); +} diff --git a/packages/worlds-explorer/src/app/(home)/TablesViewer.tsx b/packages/worlds-explorer/src/app/(home)/TablesViewer.tsx new file mode 100644 index 0000000000..526469768b --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/TablesViewer.tsx @@ -0,0 +1,304 @@ +import _ from "lodash"; +import { ArrowUpDown, Loader } from "lucide-react"; +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { NON_EDITABLE_TABLES } from "@/consts"; +import { EditableTableCell } from "./EditableTableCell"; +import { bufferToBigInt } from "./utils/bufferToBigInt"; + +type Props = { + table: string | undefined; + query: string | undefined; +}; + +export function TablesViewer({ table: selectedTable, query }: Props) { + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [columnVisibility, setColumnVisibility] = useState({}); + const [rowSelection, setRowSelection] = useState({}); + const [globalFilter, setGlobalFilter] = useState(""); + const [showAllColumns, setShowAllColumns] = useState(false); + + const { data: schema } = useQuery({ + queryKey: ["schema", { table: selectedTable }], + queryFn: async () => { + const response = await fetch(`/api/schema?table=${selectedTable}`); + return response.json(); + }, + select: (data) => { + return data.schema + .filter((column: { name: string }) => { + if (showAllColumns) { + return true; + } + return !column.name.startsWith("__"); + }) + .map((column: { name: string; type: string }) => { + return { + ...column, + name: _.camelCase(column.name), + }; + }); + }, + }); + + const { data: rows } = useQuery({ + queryKey: ["rows", { query }], + queryFn: async () => { + const response = await fetch(`/api/rows?query=${query}`); + return response.json(); + }, + select: (data) => { + return data.rows.map((row: object) => { + return Object.fromEntries( + Object.entries(row).map(([key, value]) => { + if (value?.type === "Buffer") { + return [key, bufferToBigInt(value?.data)]; + } + return [key, value]; + }), + ); + }); + }, + enabled: Boolean(selectedTable) && Boolean(query), + refetchInterval: 1000, + }); + + const { data: mudTableConfig } = useQuery({ + queryKey: ["table", { selectedTable }], + queryFn: async () => { + const response = await fetch(`/api/table?tableId=${selectedTable}`); + return response.json(); + }, + select: (data) => { + return { + ...data.table, + key_schema: JSON.parse(data.table.key_schema).json, + value_schema: JSON.parse(data.table.value_schema).json, + }; + }, + enabled: Boolean(selectedTable), + }); + + const columns: ColumnDef<{}>[] = schema?.map( + ({ name, type }: { name: string; type: string }) => { + return { + accessorKey: name, + header: ({ + column, + }: { + column: { + toggleSorting: (ascending: boolean) => void; + getIsSorted: () => "asc" | "desc" | undefined; + }; + }) => { + return ( + + ); + }, + cell: ({ + row, + }: { + row: { + getValue: (name: string) => string; + }; + }) => { + const keysSchema = Object.keys(mudTableConfig?.key_schema || {}); + const keyTuple = keysSchema.map((key) => row.getValue(key)); + const value = row.getValue(name); + if ( + (selectedTable && NON_EDITABLE_TABLES.includes(selectedTable)) || + keysSchema.includes(name) + ) { + return value?.toString(); + } + + return ( + + ); + }, + }; + }, + ); + + const table = useReactTable({ + data: rows, + columns, + initialState: { + pagination: { + pageSize: 50, + }, + }, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + onGlobalFilterChange: setGlobalFilter, + globalFilterFn: "includesString", + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + globalFilter, + }, + }); + + if (!schema || !rows) { + return ( +
+ +
+ ); + } + + return ( + <> +
+ table.setGlobalFilter(event.target.value)} + className="max-w-sm rounded border px-2 py-1" + /> + +
+ { + setShowAllColumns(!showAllColumns); + }} + /> +
+ +
+
+
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ + +
+
+ + ); +} diff --git a/packages/worlds-explorer/src/app/(home)/abi.ts b/packages/worlds-explorer/src/app/(home)/abi.ts new file mode 100644 index 0000000000..e66d896120 --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/abi.ts @@ -0,0 +1,2066 @@ +export const abi = [ + { + type: "function", + name: "app__addTask", + inputs: [ + { + name: "description", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "id", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "app__completeTask", + inputs: [ + { + name: "id", + type: "bytes32", + internalType: "bytes32", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "app__deleteTask", + inputs: [ + { + name: "id", + type: "bytes32", + internalType: "bytes32", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "app__resetTask", + inputs: [ + { + name: "id", + type: "bytes32", + internalType: "bytes32", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "batchCall", + inputs: [ + { + name: "systemCalls", + type: "tuple[]", + internalType: "struct SystemCallData[]", + components: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "returnDatas", + type: "bytes[]", + internalType: "bytes[]", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "batchCallFrom", + inputs: [ + { + name: "systemCalls", + type: "tuple[]", + internalType: "struct SystemCallFromData[]", + components: [ + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "returnDatas", + type: "bytes[]", + internalType: "bytes[]", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "call", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "payable", + }, + { + type: "function", + name: "callFrom", + inputs: [ + { + name: "delegator", + type: "address", + internalType: "address", + }, + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "payable", + }, + { + type: "function", + name: "creator", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "deleteRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "getDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getDynamicFieldLength", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getDynamicFieldSlice", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "start", + type: "uint256", + internalType: "uint256", + }, + { + name: "end", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getFieldLayout", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [ + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getFieldLength", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getFieldLength", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getKeySchema", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [ + { + name: "keySchema", + type: "bytes32", + internalType: "Schema", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "staticData", + type: "bytes", + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + ], + outputs: [ + { + name: "staticData", + type: "bytes", + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getStaticField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getValueSchema", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [ + { + name: "valueSchema", + type: "bytes32", + internalType: "Schema", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "grantAccess", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "grantee", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "initialize", + inputs: [ + { + name: "initModule", + type: "address", + internalType: "contract IModule", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "installModule", + inputs: [ + { + name: "module", + type: "address", + internalType: "contract IModule", + }, + { + name: "encodedArgs", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "installRootModule", + inputs: [ + { + name: "module", + type: "address", + internalType: "contract IModule", + }, + { + name: "encodedArgs", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "popFromDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "byteLengthToPop", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "pushToDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "dataToPush", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerDelegation", + inputs: [ + { + name: "delegatee", + type: "address", + internalType: "address", + }, + { + name: "delegationControlId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "initCallData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerFunctionSelector", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "systemFunctionSignature", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "worldFunctionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerNamespace", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerNamespaceDelegation", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "delegationControlId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "initCallData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerRootFunctionSelector", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "worldFunctionSignature", + type: "string", + internalType: "string", + }, + { + name: "systemFunctionSignature", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "worldFunctionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerStoreHook", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract IStoreHook", + }, + { + name: "enabledHooksBitmap", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerSystem", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "system", + type: "address", + internalType: "contract System", + }, + { + name: "publicAccess", + type: "bool", + internalType: "bool", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerSystemHook", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract ISystemHook", + }, + { + name: "enabledHooksBitmap", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerTable", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + { + name: "keySchema", + type: "bytes32", + internalType: "Schema", + }, + { + name: "valueSchema", + type: "bytes32", + internalType: "Schema", + }, + { + name: "keyNames", + type: "string[]", + internalType: "string[]", + }, + { + name: "fieldNames", + type: "string[]", + internalType: "string[]", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "renounceOwnership", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "revokeAccess", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "grantee", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "staticData", + type: "bytes", + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setStaticField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "spliceDynamicData", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "startWithinField", + type: "uint40", + internalType: "uint40", + }, + { + name: "deleteCount", + type: "uint40", + internalType: "uint40", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "spliceStaticData", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "start", + type: "uint48", + internalType: "uint48", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "storeVersion", + inputs: [], + outputs: [ + { + name: "version", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "transferBalanceToAddress", + inputs: [ + { + name: "fromNamespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "toAddress", + type: "address", + internalType: "address", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferBalanceToNamespace", + inputs: [ + { + name: "fromNamespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "toNamespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferOwnership", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "newOwner", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterDelegation", + inputs: [ + { + name: "delegatee", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterNamespaceDelegation", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterStoreHook", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract IStoreHook", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterSystemHook", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract ISystemHook", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "worldVersion", + inputs: [], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "event", + name: "HelloStore", + inputs: [ + { + name: "storeVersion", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "HelloWorld", + inputs: [ + { + name: "worldVersion", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_DeleteRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_SetRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + { + name: "staticData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + indexed: false, + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_SpliceDynamicData", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + indexed: false, + internalType: "uint8", + }, + { + name: "start", + type: "uint48", + indexed: false, + internalType: "uint48", + }, + { + name: "deleteCount", + type: "uint40", + indexed: false, + internalType: "uint40", + }, + { + name: "encodedLengths", + type: "bytes32", + indexed: false, + internalType: "EncodedLengths", + }, + { + name: "data", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_SpliceStaticData", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + { + name: "start", + type: "uint48", + indexed: false, + internalType: "uint48", + }, + { + name: "data", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "EncodedLengths_InvalidLength", + inputs: [ + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_Empty", + inputs: [], + }, + { + type: "error", + name: "FieldLayout_InvalidStaticDataLength", + inputs: [ + { + name: "staticDataLength", + type: "uint256", + internalType: "uint256", + }, + { + name: "computedStaticDataLength", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_StaticLengthDoesNotFitInAWord", + inputs: [ + { + name: "index", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_StaticLengthIsNotZero", + inputs: [ + { + name: "index", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_StaticLengthIsZero", + inputs: [ + { + name: "index", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_TooManyDynamicFields", + inputs: [ + { + name: "numFields", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxFields", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_TooManyFields", + inputs: [ + { + name: "numFields", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxFields", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Module_AlreadyInstalled", + inputs: [], + }, + { + type: "error", + name: "Module_MissingDependency", + inputs: [ + { + name: "dependency", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "Module_NonRootInstallNotSupported", + inputs: [], + }, + { + type: "error", + name: "Module_RootInstallNotSupported", + inputs: [], + }, + { + type: "error", + name: "Schema_InvalidLength", + inputs: [ + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Schema_StaticTypeAfterDynamicType", + inputs: [], + }, + { + type: "error", + name: "Slice_OutOfBounds", + inputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "start", + type: "uint256", + internalType: "uint256", + }, + { + name: "end", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_IndexOutOfBounds", + inputs: [ + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + { + name: "accessedIndex", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidBounds", + inputs: [ + { + name: "start", + type: "uint256", + internalType: "uint256", + }, + { + name: "end", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidFieldNamesLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidKeyNamesLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidResourceType", + inputs: [ + { + name: "expected", + type: "bytes2", + internalType: "bytes2", + }, + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "Store_InvalidSplice", + inputs: [ + { + name: "startWithinField", + type: "uint40", + internalType: "uint40", + }, + { + name: "deleteCount", + type: "uint40", + internalType: "uint40", + }, + { + name: "fieldLength", + type: "uint40", + internalType: "uint40", + }, + ], + }, + { + type: "error", + name: "Store_InvalidStaticDataLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidValueSchemaDynamicLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidValueSchemaLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidValueSchemaStaticLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_TableAlreadyExists", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "tableIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "Store_TableNotFound", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "tableIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_AccessDenied", + inputs: [ + { + name: "resource", + type: "string", + internalType: "string", + }, + { + name: "caller", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "World_AlreadyInitialized", + inputs: [], + }, + { + type: "error", + name: "World_CallbackNotAllowed", + inputs: [ + { + name: "functionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_DelegationNotFound", + inputs: [ + { + name: "delegator", + type: "address", + internalType: "address", + }, + { + name: "delegatee", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "World_FunctionSelectorAlreadyExists", + inputs: [ + { + name: "functionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_FunctionSelectorNotFound", + inputs: [ + { + name: "functionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_InsufficientBalance", + inputs: [ + { + name: "balance", + type: "uint256", + internalType: "uint256", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "World_InterfaceNotSupported", + inputs: [ + { + name: "contractAddress", + type: "address", + internalType: "address", + }, + { + name: "interfaceId", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_InvalidNamespace", + inputs: [ + { + name: "namespace", + type: "bytes14", + internalType: "bytes14", + }, + ], + }, + { + type: "error", + name: "World_InvalidResourceId", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_InvalidResourceType", + inputs: [ + { + name: "expected", + type: "bytes2", + internalType: "bytes2", + }, + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_ResourceAlreadyExists", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_ResourceNotFound", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_SystemAlreadyExists", + inputs: [ + { + name: "system", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "World_UnlimitedDelegationNotAllowed", + inputs: [], + }, +]; diff --git a/packages/worlds-explorer/src/app/(home)/page.tsx b/packages/worlds-explorer/src/app/(home)/page.tsx new file mode 100644 index 0000000000..a6763579b4 --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/page.tsx @@ -0,0 +1,14 @@ +import { Suspense } from "react"; +import { DataExplorer } from "./DataExplorer"; + +export default function Home() { + return ( + <> +
+ + + +
+ + ); +} diff --git a/packages/worlds-explorer/src/app/(home)/utils/bufferToBigInt.ts b/packages/worlds-explorer/src/app/(home)/utils/bufferToBigInt.ts new file mode 100644 index 0000000000..302d98d3d1 --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/utils/bufferToBigInt.ts @@ -0,0 +1,3 @@ +export function bufferToBigInt(bufferData: number[]) { + return BigInt(Buffer.from(bufferData).toString()); +} diff --git a/packages/worlds-explorer/src/app/(home)/utils/getFieldIdx.ts b/packages/worlds-explorer/src/app/(home)/utils/getFieldIdx.ts new file mode 100644 index 0000000000..215b5c44b9 --- /dev/null +++ b/packages/worlds-explorer/src/app/(home)/utils/getFieldIdx.ts @@ -0,0 +1,16 @@ +import { + isDynamicAbiType, + isStaticAbiType, +} from "@latticexyz/schema-type/internal"; + +export function getFieldIdx(valueSchema, fieldName) { + const fieldNames = [ + ...Object.entries(valueSchema) + .filter(([, fieldType]) => isStaticAbiType(fieldType)) + .map(([fieldName]) => fieldName), + ...Object.entries(valueSchema) + .filter(([, fieldType]) => isDynamicAbiType(fieldType)) + .map(([fieldName]) => fieldName), + ]; + return fieldNames.indexOf(fieldName); +} diff --git a/packages/worlds-explorer/src/app/_providers.tsx b/packages/worlds-explorer/src/app/_providers.tsx new file mode 100644 index 0000000000..4ca56ec072 --- /dev/null +++ b/packages/worlds-explorer/src/app/_providers.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { WagmiProvider } from "wagmi"; +import { injected, metaMask, safe } from "wagmi/connectors"; +import { ReactNode } from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { createConfig, http } from "@wagmi/core"; +import { localhost } from "@wagmi/core/chains"; + +const queryClient = new QueryClient(); + +export const wagmiConfig = createConfig({ + chains: [localhost], + connectors: [ + injected(), + metaMask({ + dappMetadata: { + name: "World Explorer", + }, + }), + safe(), + ], + transports: { + [localhost.id]: http(), + }, +}); + +export function Providers({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/packages/worlds-explorer/src/app/api/database.ts b/packages/worlds-explorer/src/app/api/database.ts new file mode 100644 index 0000000000..bfe50e13cc --- /dev/null +++ b/packages/worlds-explorer/src/app/api/database.ts @@ -0,0 +1,26 @@ +import Database from "better-sqlite3"; +import fs from "fs"; +import path from "path"; + +const DEFAULT_DB_PATH = "indexer.db"; + +export const getDatabase = () => { + let dbPath = process.env.INDEXER_DB_PATH_ABSOLUTE; + if (!dbPath) { + dbPath = path.join( + process.env.PWD as string, + process.env.INDEXER_DB_PATH || DEFAULT_DB_PATH, + ); + } + + if (!fs.existsSync(dbPath)) { + return null; + } + + const db = new Database(dbPath); + if (!db) { + return null; + } + + return db; +}; diff --git a/packages/worlds-explorer/src/app/api/rows/route.ts b/packages/worlds-explorer/src/app/api/rows/route.ts new file mode 100644 index 0000000000..04721dd1fd --- /dev/null +++ b/packages/worlds-explorer/src/app/api/rows/route.ts @@ -0,0 +1,33 @@ +import _ from "lodash"; +import { getDatabase } from "../database"; + +export const dynamic = "force-dynamic"; + +type Row = { + [key: string]: string; +}; + +type RowsResponse = Row[] | undefined; + +function convertKeysToCamelCase(rows: RowsResponse): Row[] { + if (rows == undefined) { + return []; + } + + return rows.map((row) => { + return Object.keys(row).reduce((result: { [key: string]: string }, key) => { + const camelKey = _.camelCase(key); + result[camelKey] = row[key]; + return result; + }, {}); + }); +} + +export async function GET(request: Request) { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + const query = searchParams.get("query"); + const rows = db?.prepare(query || "").all() as RowsResponse; + + return Response.json({ rows: convertKeysToCamelCase(rows) }); +} diff --git a/packages/worlds-explorer/src/app/api/schema/route.ts b/packages/worlds-explorer/src/app/api/schema/route.ts new file mode 100644 index 0000000000..325708a436 --- /dev/null +++ b/packages/worlds-explorer/src/app/api/schema/route.ts @@ -0,0 +1,12 @@ +import { getDatabase } from "../database"; + +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + const table = searchParams.get("table"); + const schema = db?.prepare(`PRAGMA table_info('${table}')`).all(); + + return Response.json({ schema }); +} diff --git a/packages/worlds-explorer/src/app/api/table/route.ts b/packages/worlds-explorer/src/app/api/table/route.ts new file mode 100644 index 0000000000..894f31cb7e --- /dev/null +++ b/packages/worlds-explorer/src/app/api/table/route.ts @@ -0,0 +1,32 @@ +import { Hex } from "viem"; +import { getDatabase } from "../database"; + +export const dynamic = "force-dynamic"; + +export type TableConfig = { + address: Hex; + id: string; + key_schema: Record; + last_error: string | null; + name: string; + namespace: string; + schema_version: number; + table_id: Hex; + value_schema: Record; +}; + +export async function GET(req: Request) { + const { searchParams } = new URL(req.url); + const tableId = searchParams.get("tableId") as Hex; + + if (!tableId) { + return Response.json({ error: "tableId is required" }, { status: 400 }); + } + + const db = getDatabase(); + const table = db + ?.prepare(`SELECT * FROM __mudStoreTables WHERE id='${tableId}'`) + .get(); + + return Response.json({ table }); +} diff --git a/packages/worlds-explorer/src/app/api/tables/route.ts b/packages/worlds-explorer/src/app/api/tables/route.ts new file mode 100644 index 0000000000..bd93e6ab8d --- /dev/null +++ b/packages/worlds-explorer/src/app/api/tables/route.ts @@ -0,0 +1,12 @@ +import { getDatabase } from "../database"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + const db = getDatabase(); + const tables = db + ?.prepare("SELECT name FROM sqlite_master WHERE type='table'") + .all(); + + return Response.json({ tables }); +} diff --git a/packages/worlds-explorer/src/app/api/world/abi.ts b/packages/worlds-explorer/src/app/api/world/abi.ts new file mode 100644 index 0000000000..86d5efc97e --- /dev/null +++ b/packages/worlds-explorer/src/app/api/world/abi.ts @@ -0,0 +1,2008 @@ +export const abi = [ + { + type: "function", + name: "batchCall", + inputs: [ + { + name: "systemCalls", + type: "tuple[]", + internalType: "struct SystemCallData[]", + components: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "returnDatas", + type: "bytes[]", + internalType: "bytes[]", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "batchCallFrom", + inputs: [ + { + name: "systemCalls", + type: "tuple[]", + internalType: "struct SystemCallFromData[]", + components: [ + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "returnDatas", + type: "bytes[]", + internalType: "bytes[]", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "call", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "payable", + }, + { + type: "function", + name: "callFrom", + inputs: [ + { + name: "delegator", + type: "address", + internalType: "address", + }, + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "payable", + }, + { + type: "function", + name: "creator", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "deleteRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "getDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getDynamicFieldLength", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getDynamicFieldSlice", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "start", + type: "uint256", + internalType: "uint256", + }, + { + name: "end", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getFieldLayout", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [ + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getFieldLength", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getFieldLength", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getKeySchema", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [ + { + name: "keySchema", + type: "bytes32", + internalType: "Schema", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "staticData", + type: "bytes", + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + ], + outputs: [ + { + name: "staticData", + type: "bytes", + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getStaticField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getValueSchema", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [ + { + name: "valueSchema", + type: "bytes32", + internalType: "Schema", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "grantAccess", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "grantee", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "initialize", + inputs: [ + { + name: "initModule", + type: "address", + internalType: "contract IModule", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "installModule", + inputs: [ + { + name: "module", + type: "address", + internalType: "contract IModule", + }, + { + name: "encodedArgs", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "installRootModule", + inputs: [ + { + name: "module", + type: "address", + internalType: "contract IModule", + }, + { + name: "encodedArgs", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "popFromDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "byteLengthToPop", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "pushToDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "dataToPush", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerDelegation", + inputs: [ + { + name: "delegatee", + type: "address", + internalType: "address", + }, + { + name: "delegationControlId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "initCallData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerFunctionSelector", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "systemFunctionSignature", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "worldFunctionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerNamespace", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerNamespaceDelegation", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "delegationControlId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "initCallData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerRootFunctionSelector", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "worldFunctionSignature", + type: "string", + internalType: "string", + }, + { + name: "systemFunctionSignature", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "worldFunctionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerStoreHook", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract IStoreHook", + }, + { + name: "enabledHooksBitmap", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerSystem", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "system", + type: "address", + internalType: "contract System", + }, + { + name: "publicAccess", + type: "bool", + internalType: "bool", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerSystemHook", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract ISystemHook", + }, + { + name: "enabledHooksBitmap", + type: "uint8", + internalType: "uint8", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "registerTable", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + { + name: "keySchema", + type: "bytes32", + internalType: "Schema", + }, + { + name: "valueSchema", + type: "bytes32", + internalType: "Schema", + }, + { + name: "keyNames", + type: "string[]", + internalType: "string[]", + }, + { + name: "fieldNames", + type: "string[]", + internalType: "string[]", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "renounceOwnership", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "revokeAccess", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "grantee", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setDynamicField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "staticData", + type: "bytes", + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setStaticField", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "fieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "fieldLayout", + type: "bytes32", + internalType: "FieldLayout", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "spliceDynamicData", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + internalType: "uint8", + }, + { + name: "startWithinField", + type: "uint40", + internalType: "uint40", + }, + { + name: "deleteCount", + type: "uint40", + internalType: "uint40", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "spliceStaticData", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + internalType: "bytes32[]", + }, + { + name: "start", + type: "uint48", + internalType: "uint48", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "storeVersion", + inputs: [], + outputs: [ + { + name: "version", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "transferBalanceToAddress", + inputs: [ + { + name: "fromNamespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "toAddress", + type: "address", + internalType: "address", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferBalanceToNamespace", + inputs: [ + { + name: "fromNamespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "toNamespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferOwnership", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "newOwner", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterDelegation", + inputs: [ + { + name: "delegatee", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterNamespaceDelegation", + inputs: [ + { + name: "namespaceId", + type: "bytes32", + internalType: "ResourceId", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterStoreHook", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract IStoreHook", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unregisterSystemHook", + inputs: [ + { + name: "systemId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "hookAddress", + type: "address", + internalType: "contract ISystemHook", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "worldVersion", + inputs: [], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "event", + name: "HelloStore", + inputs: [ + { + name: "storeVersion", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "HelloWorld", + inputs: [ + { + name: "worldVersion", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_DeleteRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_SetRecord", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + { + name: "staticData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + { + name: "encodedLengths", + type: "bytes32", + indexed: false, + internalType: "EncodedLengths", + }, + { + name: "dynamicData", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_SpliceDynamicData", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + { + name: "dynamicFieldIndex", + type: "uint8", + indexed: false, + internalType: "uint8", + }, + { + name: "start", + type: "uint48", + indexed: false, + internalType: "uint48", + }, + { + name: "deleteCount", + type: "uint40", + indexed: false, + internalType: "uint40", + }, + { + name: "encodedLengths", + type: "bytes32", + indexed: false, + internalType: "EncodedLengths", + }, + { + name: "data", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Store_SpliceStaticData", + inputs: [ + { + name: "tableId", + type: "bytes32", + indexed: true, + internalType: "ResourceId", + }, + { + name: "keyTuple", + type: "bytes32[]", + indexed: false, + internalType: "bytes32[]", + }, + { + name: "start", + type: "uint48", + indexed: false, + internalType: "uint48", + }, + { + name: "data", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "EncodedLengths_InvalidLength", + inputs: [ + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_Empty", + inputs: [], + }, + { + type: "error", + name: "FieldLayout_InvalidStaticDataLength", + inputs: [ + { + name: "staticDataLength", + type: "uint256", + internalType: "uint256", + }, + { + name: "computedStaticDataLength", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_StaticLengthDoesNotFitInAWord", + inputs: [ + { + name: "index", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_StaticLengthIsNotZero", + inputs: [ + { + name: "index", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_StaticLengthIsZero", + inputs: [ + { + name: "index", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_TooManyDynamicFields", + inputs: [ + { + name: "numFields", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxFields", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "FieldLayout_TooManyFields", + inputs: [ + { + name: "numFields", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxFields", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Module_AlreadyInstalled", + inputs: [], + }, + { + type: "error", + name: "Module_MissingDependency", + inputs: [ + { + name: "dependency", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "Module_NonRootInstallNotSupported", + inputs: [], + }, + { + type: "error", + name: "Module_RootInstallNotSupported", + inputs: [], + }, + { + type: "error", + name: "Schema_InvalidLength", + inputs: [ + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Schema_StaticTypeAfterDynamicType", + inputs: [], + }, + { + type: "error", + name: "Slice_OutOfBounds", + inputs: [ + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "start", + type: "uint256", + internalType: "uint256", + }, + { + name: "end", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_IndexOutOfBounds", + inputs: [ + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + { + name: "accessedIndex", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidBounds", + inputs: [ + { + name: "start", + type: "uint256", + internalType: "uint256", + }, + { + name: "end", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidFieldNamesLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidKeyNamesLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidResourceType", + inputs: [ + { + name: "expected", + type: "bytes2", + internalType: "bytes2", + }, + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "Store_InvalidSplice", + inputs: [ + { + name: "startWithinField", + type: "uint40", + internalType: "uint40", + }, + { + name: "deleteCount", + type: "uint40", + internalType: "uint40", + }, + { + name: "fieldLength", + type: "uint40", + internalType: "uint40", + }, + ], + }, + { + type: "error", + name: "Store_InvalidStaticDataLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidValueSchemaDynamicLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidValueSchemaLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_InvalidValueSchemaStaticLength", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256", + }, + { + name: "received", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "Store_TableAlreadyExists", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "tableIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "Store_TableNotFound", + inputs: [ + { + name: "tableId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "tableIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_AccessDenied", + inputs: [ + { + name: "resource", + type: "string", + internalType: "string", + }, + { + name: "caller", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "World_AlreadyInitialized", + inputs: [], + }, + { + type: "error", + name: "World_CallbackNotAllowed", + inputs: [ + { + name: "functionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_DelegationNotFound", + inputs: [ + { + name: "delegator", + type: "address", + internalType: "address", + }, + { + name: "delegatee", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "World_FunctionSelectorAlreadyExists", + inputs: [ + { + name: "functionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_FunctionSelectorNotFound", + inputs: [ + { + name: "functionSelector", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_InsufficientBalance", + inputs: [ + { + name: "balance", + type: "uint256", + internalType: "uint256", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "World_InterfaceNotSupported", + inputs: [ + { + name: "contractAddress", + type: "address", + internalType: "address", + }, + { + name: "interfaceId", + type: "bytes4", + internalType: "bytes4", + }, + ], + }, + { + type: "error", + name: "World_InvalidNamespace", + inputs: [ + { + name: "namespace", + type: "bytes14", + internalType: "bytes14", + }, + ], + }, + { + type: "error", + name: "World_InvalidResourceId", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_InvalidResourceType", + inputs: [ + { + name: "expected", + type: "bytes2", + internalType: "bytes2", + }, + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_ResourceAlreadyExists", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_ResourceNotFound", + inputs: [ + { + name: "resourceId", + type: "bytes32", + internalType: "ResourceId", + }, + { + name: "resourceIdString", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "World_SystemAlreadyExists", + inputs: [ + { + name: "system", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "World_UnlimitedDelegationNotAllowed", + inputs: [], + }, +]; diff --git a/packages/worlds-explorer/src/app/api/world/route.ts b/packages/worlds-explorer/src/app/api/world/route.ts new file mode 100644 index 0000000000..81a83cf995 --- /dev/null +++ b/packages/worlds-explorer/src/app/api/world/route.ts @@ -0,0 +1,47 @@ +import { Hex, createWalletClient, http } from "viem"; +import { getWorldAbi, getWorldDeploy } from "@latticexyz/cli"; +import { getRpcUrl } from "@latticexyz/common/foundry"; +import { abi as defaultAbi } from "./abi"; +import { deduplicateABI } from "./utils/deduplicateABI"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: Request) { + const { searchParams } = new URL(req.url); + const worldAddress = searchParams.get("address") as Hex; + + if (!worldAddress) { + return Response.json({ error: "address is required" }, { status: 400 }); + } + + const profile = process.env.FOUNDRY_PROFILE; + const rpc = await getRpcUrl(profile); + const client = createWalletClient({ + transport: http(rpc), + }); + + try { + const worldDeploy = await getWorldDeploy(client, worldAddress); + const worldAbi = await getWorldAbi({ + client, + worldDeploy, + }); + + const combinedABI = [...worldAbi, ...defaultAbi]; + const filteredABI = deduplicateABI(combinedABI).sort((a, b) => + a.name.localeCompare(b.name), + ); + const sortedABI = filteredABI.sort((a, b) => a.name.localeCompare(b.name)); + + return Response.json({ abi: sortedABI }); + } catch (error: unknown) { + if (error instanceof Error) { + return Response.json({ error: error.message }, { status: 400 }); + } else { + return Response.json( + { error: "An unknown error occurred" }, + { status: 400 }, + ); + } + } +} diff --git a/packages/worlds-explorer/src/app/api/world/utils/deduplicateABI.ts b/packages/worlds-explorer/src/app/api/world/utils/deduplicateABI.ts new file mode 100644 index 0000000000..270764e636 --- /dev/null +++ b/packages/worlds-explorer/src/app/api/world/utils/deduplicateABI.ts @@ -0,0 +1,21 @@ +function hasNamedInputs(entry) { + return entry.inputs && entry.inputs.some((input) => input.name); +} + +export function deduplicateABI(abi) { + const uniqueEntries = new Map(); + + abi.forEach((entry) => { + const key = `${entry.type}_${entry.name}`; + const existingEntry = uniqueEntries.get(key); + + if ( + !existingEntry || + (hasNamedInputs(entry) && !hasNamedInputs(existingEntry)) + ) { + uniqueEntries.set(key, entry); + } + }); + + return Array.from(uniqueEntries.values()); +} diff --git a/packages/worlds-explorer/src/app/favicon.ico b/packages/worlds-explorer/src/app/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/packages/worlds-explorer/src/app/favicon.ico differ diff --git a/packages/worlds-explorer/src/app/globals.css b/packages/worlds-explorer/src/app/globals.css new file mode 100644 index 0000000000..e3adb31bcb --- /dev/null +++ b/packages/worlds-explorer/src/app/globals.css @@ -0,0 +1,69 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 20 14.3% 4.1%; + --card: 0 0% 100%; + --card-foreground: 20 14.3% 4.1%; + --popover: 0 0% 100%; + --popover-foreground: 20 14.3% 4.1%; + --primary: 24.6 95% 53.1%; + --primary-foreground: 60 9.1% 97.8%; + --secondary: 60 4.8% 95.9%; + --secondary-foreground: 24 9.8% 10%; + --muted: 60 4.8% 95.9%; + --muted-foreground: 25 5.3% 44.7%; + --accent: 60 4.8% 95.9%; + --accent-foreground: 24 9.8% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 20 5.9% 90%; + --input: 20 5.9% 90%; + --ring: 24.6 95% 53.1%; + --radius: 0.5rem; + --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%; + } + + .dark { + --background: 20 14.3% 4.1%; + --foreground: 60 9.1% 97.8%; + --card: 20 14.3% 4.1%; + --card-foreground: 60 9.1% 97.8%; + --popover: 20 14.3% 4.1%; + --popover-foreground: 60 9.1% 97.8%; + --primary: 20.5 90.2% 48.2%; + --primary-foreground: 60 9.1% 97.8%; + --secondary: 12 6.5% 15.1%; + --secondary-foreground: 60 9.1% 97.8%; + --muted: 12 6.5% 15.1%; + --muted-foreground: 24 5.4% 63.9%; + --accent: 12 6.5% 15.1%; + --accent-foreground: 60 9.1% 97.8%; + --destructive: 0 72.2% 50.6%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 12 6.5% 15.1%; + --input: 12 6.5% 15.1%; + --ring: 20.5 90.2% 48.2%; + --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/packages/worlds-explorer/src/app/interact/Form.tsx b/packages/worlds-explorer/src/app/interact/Form.tsx new file mode 100644 index 0000000000..a91a046773 --- /dev/null +++ b/packages/worlds-explorer/src/app/interact/Form.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { Coins, Eye, Send } from "lucide-react"; +import { Abi, AbiFunction } from "viem"; +import { useDeferredValue, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { useHashState } from "@/hooks/useHash"; +import { cn } from "@/lib/utils"; +import { FunctionField } from "./FunctionField"; + +type Props = { + data: { + abi: Abi; + }; +}; + +export function Form({ data }: Props) { + const [hash] = useHashState(); + const [filterValue, setFilterValue] = useState(""); + const deferredFilterValue = useDeferredValue(filterValue); + const abiFunctions = data.abi.filter( + (abi) => (abi as AbiFunction).type === "function", + ); + const filteredFunctions = abiFunctions.filter((abi) => { + return (abi as AbiFunction).name + .toLowerCase() + .includes(deferredFilterValue.toLowerCase()); + }); + + return ( +
+
+
+

+ Jump to: +

+ + { + setFilterValue(evt.target.value); + }} + /> + + +
+
+ +
+ {filteredFunctions.map((abi) => { + return ( + + ); + })} +
+
+ ); +} diff --git a/packages/worlds-explorer/src/app/interact/FunctionField.tsx b/packages/worlds-explorer/src/app/interact/FunctionField.tsx new file mode 100644 index 0000000000..51ba510089 --- /dev/null +++ b/packages/worlds-explorer/src/app/interact/FunctionField.tsx @@ -0,0 +1,184 @@ +"use client"; + +import { Coins, Eye, Send } from "lucide-react"; +import { toast } from "sonner"; +import { Abi, AbiFunction, parseEventLogs } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { useWriteContract } from "wagmi"; +import { z } from "zod"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { readContract, waitForTransactionReceipt } from "@wagmi/core"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { ACCOUNT_PRIVATE_KEYS } from "@/consts"; +import { useWorldAddress } from "@/hooks/useWorldAddress"; +import { useStore } from "@/store"; +import { wagmiConfig } from "../_providers"; + +type Props = { + abi: AbiFunction; +}; + +const formSchema = z.object({ + inputs: z.array(z.string()), + value: z.string().optional(), +}); + +export function FunctionField({ abi }: Props) { + const [result, setResult] = useState(null); + const { account, fetchBalances } = useStore(); + const worldAddress = useWorldAddress(); + const { writeContractAsync } = useWriteContract(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + inputs: [], + }, + }); + + async function onSubmit(values: z.infer) { + if (abi.stateMutability === "pure" || abi.stateMutability === "view") { + const result = await readContract(wagmiConfig, { + abi: [abi] as Abi, + address: worldAddress, + functionName: abi.name, + args: values.inputs, + }); + + setResult(result as string); + } else { + const toastId = toast.loading("Transaction submitted"); + + try { + const txHash = await writeContractAsync({ + account: privateKeyToAccount(ACCOUNT_PRIVATE_KEYS[account]), + abi: [abi] as Abi, + address: worldAddress, + functionName: abi.name, + args: values.inputs, + ...(values.value && { value: BigInt(values.value) }), + }); + + const transactionReceipt = await waitForTransactionReceipt( + wagmiConfig, + { + hash: txHash, + pollingInterval: 100, + }, + ); + + const logs = parseEventLogs({ + abi: [abi] as Abi, + logs: transactionReceipt.logs, + }); + console.log("logs:", logs); + + toast.success(`Transaction successful with hash: ${txHash}`, { + id: toastId, + }); + + console.log("result:", txHash, transactionReceipt); + } catch (error) { + console.log("error:", error); + + const msg = error.message; + toast.error(msg, { + id: toastId, + }); + } finally { + fetchBalances(); + } + } + } + + const inputsLabel = abi?.inputs.map((input) => input.type).join(", "); + return ( +
+ +

+ {abi?.name} + + {inputsLabel && ` (${inputsLabel})`} + + + {abi.stateMutability === "payable" && ( + + )} + {(abi.stateMutability === "view" || + abi.stateMutability === "pure") && ( + + )} + {abi.stateMutability === "nonpayable" && ( + + )} + +

+ + {abi?.inputs.map((input, idx) => { + return ( + ( + + {input.name} + + + + + + )} + /> + ); + })} + + {abi.stateMutability === "payable" && ( + ( + + ETH value + + + + + + )} + /> + )} + + + + {result && ( +
{result}
+ )} + + + + + ); +} diff --git a/packages/worlds-explorer/src/app/interact/page.tsx b/packages/worlds-explorer/src/app/interact/page.tsx new file mode 100644 index 0000000000..ff7a1ba3d3 --- /dev/null +++ b/packages/worlds-explorer/src/app/interact/page.tsx @@ -0,0 +1,26 @@ +import { headers } from "next/headers"; +import { Form } from "./Form"; + +async function getABI() { + const headersList = headers(); + const protocol = headersList.get("x-forwarded-proto"); + const host = headersList.get("host"); + + const res = await fetch( + `${protocol}://${host}/api/world?address=${process.env.NEXT_PUBLIC_WORLD_ADDRESS}`, + ); + if (!res.ok) { + throw new Error("Failed to fetch data"); + } + + return res.json(); +} + +export default async function Interact() { + const data = await getABI(); + return ( + <> +
+ + ); +} diff --git a/packages/worlds-explorer/src/app/layout.tsx b/packages/worlds-explorer/src/app/layout.tsx new file mode 100644 index 0000000000..038fb813a0 --- /dev/null +++ b/packages/worlds-explorer/src/app/layout.tsx @@ -0,0 +1,51 @@ +import type { Metadata } from "next"; +import { Inter, JetBrains_Mono } from "next/font/google"; +import { Toaster } from "sonner"; +import { Theme } from "@radix-ui/themes"; +import "@radix-ui/themes/styles.css"; +import { Navigation } from "@/components/navigation"; +import { Providers } from "./_providers"; +import "./globals.css"; + +const inter = Inter({ + subsets: ["latin"], + display: "swap", + variable: "--font-inter", +}); + +const jetbrains = JetBrains_Mono({ + subsets: ["latin"], + variable: "--font-jetbrains-mono", +}); + +export const metadata: Metadata = { + title: "Worlds Explorer", + description: "Worlds Explorer", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + +
+ + {children} +
+ +
+
+ + + ); +} diff --git a/packages/worlds-explorer/src/components/account-select.tsx b/packages/worlds-explorer/src/components/account-select.tsx new file mode 100644 index 0000000000..67aa0fb342 --- /dev/null +++ b/packages/worlds-explorer/src/components/account-select.tsx @@ -0,0 +1,39 @@ +import { formatEther } from "viem"; +import { useEffect } from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ACCOUNTS } from "@/consts"; +import { useStore } from "@/store"; + +export function AccountSelect() { + const { account, setAccount, balances, fetchBalances } = useStore(); + + useEffect(() => { + fetchBalances(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +} diff --git a/packages/worlds-explorer/src/components/account.tsx b/packages/worlds-explorer/src/components/account.tsx new file mode 100644 index 0000000000..286e8baf90 --- /dev/null +++ b/packages/worlds-explorer/src/components/account.tsx @@ -0,0 +1,16 @@ +import { useAccount, useDisconnect, useEnsAvatar, useEnsName } from "wagmi"; + +export function Account() { + const { address } = useAccount(); + const { disconnect } = useDisconnect(); + const { data: ensName } = useEnsName({ address }); + const { data: ensAvatar } = useEnsAvatar({ name: ensName! }); + + return ( +
+ {ensAvatar && ENS Avatar} + {address &&
{ensName ? `${ensName} (${address})` : address}
} + +
+ ); +} diff --git a/packages/worlds-explorer/src/components/connect-wallet.tsx b/packages/worlds-explorer/src/components/connect-wallet.tsx new file mode 100644 index 0000000000..dd497be4b8 --- /dev/null +++ b/packages/worlds-explorer/src/components/connect-wallet.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { useAccount } from "wagmi"; +import { Account } from "@/components/account"; +import { WalletOptions } from "@/components/wallet-options"; + +export function ConnectWallet() { + const { isConnected } = useAccount(); + if (isConnected) return ; + return ; +} diff --git a/packages/worlds-explorer/src/components/latest-block.tsx b/packages/worlds-explorer/src/components/latest-block.tsx new file mode 100644 index 0000000000..a7520ee485 --- /dev/null +++ b/packages/worlds-explorer/src/components/latest-block.tsx @@ -0,0 +1,25 @@ +import { useBlockNumber } from "wagmi"; + +export function LatestBlock() { + const { data: block } = useBlockNumber({ + watch: true, + }); + + if (block === undefined || block === BigInt(0)) { + return; + } + + return ( +
+
+ + {block.toString()} +
+
+ ); +} diff --git a/packages/worlds-explorer/src/components/navigation.tsx b/packages/worlds-explorer/src/components/navigation.tsx new file mode 100644 index 0000000000..d1dc846cec --- /dev/null +++ b/packages/worlds-explorer/src/components/navigation.tsx @@ -0,0 +1,47 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { LatestBlock } from "@/components/latest-block"; +import { cn } from "@/lib/utils"; +import { AccountSelect } from "./account-select"; +import { Separator } from "./ui/separator"; + +export function Navigation() { + const pathname = usePathname(); + + return ( +
+
+
+ + Data explorer + + + + Interact + +
+ +
+ + +
+
+ + +
+ ); +} diff --git a/packages/worlds-explorer/src/components/ui/button.tsx b/packages/worlds-explorer/src/components/ui/button.tsx new file mode 100644 index 0000000000..52b598fdec --- /dev/null +++ b/packages/worlds-explorer/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + // eslint-disable-next-line max-len + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/packages/worlds-explorer/src/components/ui/checkbox.tsx b/packages/worlds-explorer/src/components/ui/checkbox.tsx new file mode 100644 index 0000000000..592d5d2aa3 --- /dev/null +++ b/packages/worlds-explorer/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { Check } from "lucide-react"; +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { cn } from "@/lib/utils"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/packages/worlds-explorer/src/components/ui/dropdown-menu.tsx b/packages/worlds-explorer/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000000..f36bd7f8dd --- /dev/null +++ b/packages/worlds-explorer/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,204 @@ +"use client"; + +import { Check, ChevronRight, Circle } from "lucide-react"; +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { cn } from "@/lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/packages/worlds-explorer/src/components/ui/form.tsx b/packages/worlds-explorer/src/components/ui/form.tsx new file mode 100644 index 0000000000..df4699cfa0 --- /dev/null +++ b/packages/worlds-explorer/src/components/ui/form.tsx @@ -0,0 +1,178 @@ +"use client"; + +import * as React from "react"; +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +