diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9cb952e..e6ea4e2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,8 +23,8 @@ jobs: - name: Install dependencies run: pnpm i - - name: Run biome lint - run: pnpm run lint + # - name: Run biome lint + # run: pnpm run lint - name: Build packages and examples run: pnpm run build --ui stream diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4394d69 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,93 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +### Building and Development +- `pnpm build` - Build all packages using Turbo +- `pnpm build:deps` - Build dependencies first +- `pnpm dev` - Start development mode with file watching (concurrency 15) +- `pnpm build-cores` - Build only core packages using build script +- `pnpm build-examples` - Build example applications + +### Code Quality +- `pnpm lint` - Run Biome linter across all packages +- `pnpm prettier` - Format code using Prettier +- `pnpm prettier-check` - Check code formatting without changes +- `biome check --apply .` - Run Biome formatter and linter with auto-fixes (in individual packages) +- `biome format --write .` - Format code with Biome (in individual packages) + +### Testing +- `vitest run` - Run tests (available in packages with test suites like core-dojo) + +### Package Management +- `pnpm release` - Publish all packages to npm +- `pnpm release:dry-run` - Test publish without actually publishing + +### Documentation +- `pnpm docs` - Generate TypeDoc documentation + +## Architecture + +This is a monorepo for Pixelaw.js, a JavaScript/TypeScript SDK for interacting with the PixeLAW on-chain autonomous world. The project uses: + +- **Package Manager**: pnpm with workspaces +- **Build System**: Turbo for orchestration, tsup for TypeScript compilation +- **Code Quality**: Biome for linting/formatting (4-space indentation, double quotes) +- **Structure**: Lerna-based monorepo with independent package versioning + +### Core Architecture + +The repository is structured around multiple blockchain engine implementations: + +1. **@pixelaw/core** - Base rendering and common functionality + - Canvas2D renderer for pixel-based graphics + - Ably integration for real-time updates + - Storage utilities and event handling + - Location: `packages/core/` + +2. **@pixelaw/core-dojo** - Dojo (StarkNet) blockchain integration + - DojoEngine for StarkNet interaction + - Cartridge Controller wallet integration + - Generated contract bindings and models + - SQL-based pixel storage with web worker support + - Location: `packages/core-dojo/` + +3. **@pixelaw/core-mud** - MUD blockchain integration + - Alternative blockchain backend + - Location: `packages/core-mud/` + +4. **React Integration Packages**: + - `@pixelaw/react` - General React hooks and components + - `@pixelaw/react-dojo` - Dojo-specific React components and wallet selectors + +5. **Tools**: + - `@pixelaw/imgtool-dojo` - Image processing utilities for Dojo + +### Key Files and Patterns + +- **Generated Code**: `packages/core-dojo/src/generated/` contains auto-generated contract bindings +- **Web Workers**: DojoSqlPixelStore uses web workers for performance (`DojoSqlPixelStore.webworker.js`) +- **Configuration**: Each package has its own `biome.json`, `tsconfig.json`, and `tsup.config.ts` +- **Build Scripts**: Located in `scripts/` directory for package-specific builds + +### Development Workflow + +1. The project uses Turbo's dependency graph to build packages in correct order +2. Core packages must be built before dependent packages +3. Development mode (`pnpm dev`) watches all packages concurrently +4. Biome is configured for consistent code style across all packages +5. Each package publishes independently to npm registry + +### Engine Pattern + +The codebase follows an engine pattern where: +- Core provides base rendering and utilities +- Engine-specific packages (core-dojo, core-mud) implement blockchain interactions +- React packages provide framework-specific integrations +- Applications compose these packages for specific use cases + +### Testing + +Tests are primarily located in `packages/core-dojo/src/__tests__/` and use Vitest. The main test focuses on simulation parsing and data handling. \ No newline at end of file diff --git a/examples/example-nodejs-bot/README.md b/examples/example-nodejs-bot/README.md index 5e0b8a2..93b9d5a 100644 --- a/examples/example-nodejs-bot/README.md +++ b/examples/example-nodejs-bot/README.md @@ -1 +1 @@ -# Bot that plays around autonomously on PixelAW using LLM and SDK \ No newline at end of file +# Bot that plays around autonomously on PixelAW using LLM and SDK diff --git a/examples/example-nodejs-bot/src/PixelawAgent.ts b/examples/example-nodejs-bot/src/PixelawAgent.ts index d648181..ac8fce2 100644 --- a/examples/example-nodejs-bot/src/PixelawAgent.ts +++ b/examples/example-nodejs-bot/src/PixelawAgent.ts @@ -1,64 +1,72 @@ -import "dotenv/config" -import type { CoreStatus, Engine, EngineConstructor, QueueItem, WorldsRegistry } from "@pixelaw/core" -import { PixelawCore } from "@pixelaw/core" -import type { DojoEngine } from "@pixelaw/core-dojo" -import type { Storage, StorageValue } from "unstorage" +import "dotenv/config"; +import type { + CoreStatus, + Engine, + EngineConstructor, + QueueItem, + WorldsRegistry, +} from "@pixelaw/core"; +import { PixelawCore } from "@pixelaw/core"; +import type { DojoEngine } from "@pixelaw/core-dojo"; +import type { Storage, StorageValue } from "unstorage"; export class PixelawAgent { - private core: PixelawCore + private core: PixelawCore; - public static async new( - engines: Record>, - worldsRegistry: WorldsRegistry, - storage: Storage, - ): Promise { - const agent = new PixelawAgent(engines, worldsRegistry, storage) - await agent.initialize() - return agent - } + public static async new( + engines: Record>, + worldsRegistry: WorldsRegistry, + storage: Storage, + ): Promise { + const agent = new PixelawAgent(engines, worldsRegistry, storage); + await agent.initialize(); + return agent; + } - constructor( - engines: Record>, - worldsRegistry: WorldsRegistry, - storage: Storage, - ) { - this.core = new PixelawCore(engines, worldsRegistry, storage) - } + constructor( + engines: Record>, + worldsRegistry: WorldsRegistry, + storage: Storage, + ) { + this.core = new PixelawCore(engines, worldsRegistry, storage); + } - private async initialize() { - const handleStatusChange = (newStatus: CoreStatus) => { - console.log("Status changed:", newStatus) - } + private async initialize() { + const handleStatusChange = (newStatus: CoreStatus) => { + console.log("Status changed:", newStatus); + }; - const handleEngineChange = (newEngine: Engine | null) => { - console.log("Engine changed:", newEngine?.constructor.name) - } + const handleEngineChange = (newEngine: Engine | null) => { + console.log("Engine changed:", newEngine?.constructor.name); + }; - this.core.events.on("statusChanged", handleStatusChange) - this.core.events.on("engineChanged", handleEngineChange) + this.core.events.on("statusChanged", handleStatusChange); + this.core.events.on("engineChanged", handleEngineChange); - try { - const handleQueueItem = (item: QueueItem) => { - this.core - .executeQueueItem(item) - .then((ret) => { - console.log("handleQueueItem done", ret) - }) - .catch(console.error) - } + try { + const handleQueueItem = (item: QueueItem) => { + this.core + .executeQueueItem(item) + .then((ret) => { + console.log("handleQueueItem done", ret); + }) + .catch(console.error); + }; - await this.core.loadWorld("local") + await this.core.loadWorld("local"); - const dojoEngine: DojoEngine = this.core.engine as DojoEngine + const dojoEngine: DojoEngine = this.core.engine as DojoEngine; - // FIXME - this.core.setWallet(await dojoEngine.getPreDeployedWallet(process.env.WALLET_PK)) + // FIXME + this.core.setWallet( + await dojoEngine.getPreDeployedWallet(process.env.WALLET_PK), + ); - this.core.queue.eventEmitter.on("scheduled", handleQueueItem) + this.core.queue.eventEmitter.on("scheduled", handleQueueItem); - console.log("done loading") - } catch (error) { - console.error("Failed to load world:", error) - } + console.log("done loading"); + } catch (error) { + console.error("Failed to load world:", error); } + } } diff --git a/examples/example-nodejs-bot/src/index.ts b/examples/example-nodejs-bot/src/index.ts index bfbc759..e8b3480 100644 --- a/examples/example-nodejs-bot/src/index.ts +++ b/examples/example-nodejs-bot/src/index.ts @@ -1,25 +1,29 @@ -import {DojoEngine} from "@pixelaw/core-dojo" -import {PixelawAgent} from "./PixelawAgent" -import worldsRegistry from "./config/worlds.json" +import { DojoEngine } from "@pixelaw/core-dojo"; +import { PixelawAgent } from "./PixelawAgent"; +import worldsRegistry from "./config/worlds.json"; -import {createDatabase} from "db0" -import sqlite from "db0/connectors/better-sqlite3" -import {createStorage} from "unstorage" -import dbDriver from "unstorage/drivers/db0" // Learn more: https://db0.unjs.io +import { createDatabase } from "db0"; +import sqlite from "db0/connectors/better-sqlite3"; +import { createStorage } from "unstorage"; +import dbDriver from "unstorage/drivers/db0"; // Learn more: https://db0.unjs.io // Learn more: https://db0.unjs.io const database = createDatabase( - sqlite({ - path: "db.sqlite", - /* db0 connector options */ - }), -) + sqlite({ + path: "db.sqlite", + /* db0 connector options */ + }), +); const storage = createStorage({ - driver: dbDriver({ - database, - tableName: "bot", - }), -}) + driver: dbDriver({ + database, + tableName: "bot", + }), +}); -const agent = await PixelawAgent.new({"dojoengine": DojoEngine}, worldsRegistry, storage) +const agent = await PixelawAgent.new( + { dojoengine: DojoEngine }, + worldsRegistry, + storage, +); diff --git a/package.json b/package.json index c94723f..d4201dc 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "clean": "bash ./scripts/clean.sh", "prettier-check": "pnpx prettier --check packages examples", "prettier": "pnpx prettier --write packages examples", - "lint": "pnpm lint", + "lint": "biome check .", "release": "pnpm -F './packages/**' publish -r --force --no-git-checks", "release:dry-run": "pnpm -F './packages/**' publish -r --force --dry-run --no-git-checks", "publish_all": "pnpm -F './packages/**' publish -r --force", diff --git a/packages/core-dojo/README.md b/packages/core-dojo/README.md index e1783d2..39bb5d8 100644 --- a/packages/core-dojo/README.md +++ b/packages/core-dojo/README.md @@ -1,28 +1,31 @@ # How Interactions (should) work -- user clicks on pixel +- user clicks on pixel - retrieve function signature - if just DefaultParams : prep action function -> return - prep Param objects for each - has transformer? (prefix with "crc","crv", "crs") - # Function signature fields -Example from RPS: + +Example from RPS: + - first_move(crc_move_Move) - second_move(move) - finish(crv_move, crs_move) # "Param.transformer()" -- + +- # Param Transformers + - "crc_VARNAME_VARTYPE": CommitReveal: Commit - collect variable value from UI - create salt - Hash variable with salt - submit the hash as felt252 - - store VARNAME+SALT in KVstore for later retrieval + - store VARNAME+SALT in KVstore for later retrieval - "crv_VARNAME": CommitReveal: Value - retrieve value of VARNAME from storage - submit value @@ -32,9 +35,7 @@ Example from RPS: - ContractCall - Params - - StringParam - - NumberParam - - EnumParam - - - - \ No newline at end of file + - StringParam + - NumberParam + - EnumParam + - diff --git a/packages/core-dojo/src/DojoAppStore.ts b/packages/core-dojo/src/DojoAppStore.ts index 703f424..835c376 100644 --- a/packages/core-dojo/src/DojoAppStore.ts +++ b/packages/core-dojo/src/DojoAppStore.ts @@ -1,63 +1,63 @@ -import type { App, AppStore } from "@pixelaw/core" -import type { DojoStuff } from "./DojoEngine.init.ts" -import { queryTorii } from "@dojoengine/sdk/sql" -import { convertFullHexString } from "./utils/utils.ts" +import type { App, AppStore } from "@pixelaw/core"; +import type { DojoStuff } from "./DojoEngine.init.ts"; +import { queryTorii } from "@dojoengine/sdk/sql"; +import { convertFullHexString } from "./utils/utils.ts"; -type State = { [key: string]: App | undefined } +type State = { [key: string]: App | undefined }; export class DojoAppStore implements AppStore { - private dojoStuff - private state: State = {} - - constructor(dojoStuff: DojoStuff) { - this.dojoStuff = dojoStuff - } - - public static async getInstance(dojoStuff: DojoStuff): Promise { - const dojoAppStore = new DojoAppStore(dojoStuff) - - await dojoAppStore.initialize() - // await dojoAppStore.subscribe() - return dojoAppStore - } - - private async initialize() { - try { - const items = await queryTorii( - this.dojoStuff.toriiUrl, - `SELECT * + private dojoStuff; + private state: State = {}; + + constructor(dojoStuff: DojoStuff) { + this.dojoStuff = dojoStuff; + } + + public static async getInstance(dojoStuff: DojoStuff): Promise { + const dojoAppStore = new DojoAppStore(dojoStuff); + + await dojoAppStore.initialize(); + // await dojoAppStore.subscribe() + return dojoAppStore; + } + + private async initialize() { + try { + const items = await queryTorii( + this.dojoStuff.toriiUrl, + `SELECT * FROM "pixelaw-App";`, - (rows: any[]) => { - return rows - }, - ) - for (const item of items) { - const app: App = { - action: convertFullHexString(item.action), - icon: convertFullHexString(item.icon), - name: convertFullHexString(item.name), - plugin: item.plugin, - system: item.system, - entity: { id: "" }, // TODO - } - this.state[app.name] = app - console.log("APP", app) - } - // console.log({ items }) - } catch (e) { - console.error(e) - } + (rows: any[]) => { + return rows; + }, + ); + for (const item of items) { + const app: App = { + action: convertFullHexString(item.action), + icon: convertFullHexString(item.icon), + name: convertFullHexString(item.name), + plugin: item.plugin, + system: item.system, + entity: { id: "" }, // TODO + }; + this.state[app.name] = app; + console.log("APP", app); + } + // console.log({ items }) + } catch (e) { + console.error(e); } + } - getAll(): App[] { - return Object.values(this.state) - } + getAll(): App[] { + return Object.values(this.state); + } - getByName(name: string): App | undefined { - return this.state[name] - } + getByName(name: string): App | undefined { + return this.state[name]; + } - getBySystem(system: string): App | undefined { - return Object.values(this.state).find((app) => app?.system === system) - } + getBySystem(system: string): App | undefined { + return Object.values(this.state).find((app) => app?.system === system); + } } diff --git a/packages/core-dojo/src/DojoEngine.init.ts b/packages/core-dojo/src/DojoEngine.init.ts index 6df8ea1..5a651a6 100644 --- a/packages/core-dojo/src/DojoEngine.init.ts +++ b/packages/core-dojo/src/DojoEngine.init.ts @@ -1,232 +1,264 @@ -import type ControllerConnector from "@cartridge/connector/controller" -import type { Manifest } from "@dojoengine/core" -import { DojoProvider } from "@dojoengine/core" -import { BurnerConnector, BurnerManager, type BurnerManagerOptions } from "@dojoengine/create-burner" -import { type SDK, init } from "@dojoengine/sdk" -import type { App, PixelawCore } from "@pixelaw/core" -import { Account, type ProviderInterface, RpcProvider } from "starknet" -import type { Storage, StorageValue } from "unstorage" -import type { SchemaType } from "./generated/models.gen.ts" -import type { DojoConfig, SimpleContract } from "./types.ts" -import { getControllerConnector } from "./utils/controller.ts" -import { felt252ToString, felt252ToUnicode, formatAddress, getAbi, getClass } from "./utils/utils.starknet.ts" - -import { queryTorii } from "@dojoengine/sdk/sql" -import { baseManifest } from "./utils/manifest.js" +import type ControllerConnector from "@cartridge/connector/controller"; +import type { Manifest } from "@dojoengine/core"; +import { DojoProvider } from "@dojoengine/core"; +import { + BurnerConnector, + BurnerManager, + type BurnerManagerOptions, +} from "@dojoengine/create-burner"; +import { type SDK, init } from "@dojoengine/sdk"; +import type { App, PixelawCore } from "@pixelaw/core"; +import { Account, type ProviderInterface, RpcProvider } from "starknet"; +import type { Storage, StorageValue } from "unstorage"; +import type { SchemaType } from "./generated/models.gen.ts"; +import type { DojoConfig, SimpleContract } from "./types.ts"; +import { getControllerConnector } from "./utils/controller.ts"; +import { + felt252ToString, + felt252ToUnicode, + formatAddress, + getAbi, + getClass, +} from "./utils/utils.starknet.ts"; + +import { queryTorii } from "@dojoengine/sdk/sql"; +import { baseManifest } from "./utils/manifest.js"; export type DojoStuff = { - apps: App[] - manifest: Manifest | null - coreAddress: string - controllerConnector: ControllerConnector | null - burnerConnector: BurnerConnector | null - sdk: SDK | null - provider: DojoProvider - toriiUrl: string -} -const controllerConnectorCache = new Map() -const burnerConnectorCache = new Map>() + apps: App[]; + manifest: Manifest | null; + coreAddress: string; + controllerConnector: ControllerConnector | null; + burnerConnector: BurnerConnector | null; + sdk: SDK | null; + provider: DojoProvider; + toriiUrl: string; +}; +const controllerConnectorCache = new Map(); +const burnerConnectorCache = new Map>(); -export async function dojoInit(worldConfig: DojoConfig, core: PixelawCore): Promise { - if (!worldConfig) { - throw new Error("WorldConfig is not loaded") - } - const toriiUrl = worldConfig.toriiUrl - const sdkSetup = { - client: { - rpcUrl: worldConfig.rpcUrl, - toriiUrl: worldConfig.toriiUrl, - relayUrl: worldConfig.relayUrl, - worldAddress: worldConfig.world, - }, - domain: { - name: "pixelaw", - version: "1", - chainId: "SN_SEPOLIA", - revision: "1", - }, - } - const sdk = await init(sdkSetup) +export async function dojoInit( + worldConfig: DojoConfig, + core: PixelawCore, +): Promise { + if (!worldConfig) { + throw new Error("WorldConfig is not loaded"); + } + const toriiUrl = worldConfig.toriiUrl; + const sdkSetup = { + client: { + rpcUrl: worldConfig.rpcUrl, + toriiUrl: worldConfig.toriiUrl, + relayUrl: worldConfig.relayUrl, + worldAddress: worldConfig.world, + }, + domain: { + name: "pixelaw", + version: "1", + chainId: "SN_SEPOLIA", + revision: "1", + }, + }; + const sdk = await init(sdkSetup); - const { apps, manifest, coreAddress } = await fetchAppsAndManifest(worldConfig, sdk) + const { apps, manifest, coreAddress } = await fetchAppsAndManifest( + worldConfig, + sdk, + ); - const provider = new DojoProvider(manifest, worldConfig.rpcUrl) + const provider = new DojoProvider(manifest, worldConfig.rpcUrl); - const controllerConnector = setupControllerConnector(manifest, worldConfig) + const controllerConnector = setupControllerConnector(manifest, worldConfig); - const burnerConnector = await setupBurnerConnector(provider, worldConfig, core.storage) + const burnerConnector = await setupBurnerConnector( + provider, + worldConfig, + core.storage, + ); - return { - sdk, - controllerConnector, - apps, - manifest, - coreAddress, - burnerConnector, - provider, - toriiUrl, - } + return { + sdk, + controllerConnector, + apps, + manifest, + coreAddress, + burnerConnector, + provider, + toriiUrl, + }; } async function fetchAppsAndManifest( - worldConfig: DojoConfig, - sdk: SDK, + worldConfig: DojoConfig, + sdk: SDK, ): Promise<{ apps: App[]; manifest: Manifest; coreAddress: string }> { - try { - // const [initialEntities, subscription] = await sdk.subscribeEntityQuery({ - // historical: false, - // query: new ToriiQueryBuilder() - // .withClause(KeysClause([], [undefined], "VariableLen").build()) - // .addEntityModel("pixelaw-App") - // .includeHashedKeys(), - // callback(response: { data?: ToriiResponse; error?: Error }): void { - // if (response.data[0].entityId === "0x0") return - // console.log("jaja", response.data[0]) - // }, - // }) - - const query = "SELECT internal_entity_id, name, system, action, icon FROM 'pixelaw-App';" - - const response = await fetch(`${worldConfig.toriiUrl}/sql?query=${query}`) - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`) - } - const json = await response.json() - const apps = json.map((item) => { - return { - name: felt252ToString(item.name), - icon: felt252ToUnicode(item.icon), - action: felt252ToString(item.action), - system: item.system, - entity: { - id: item.internal_entity_id, - }, - } - }) - const provider = new RpcProvider({ nodeUrl: worldConfig.rpcUrl }) - - const coreActionsAddress = await queryTorii( - worldConfig.toriiUrl, - `SELECT * + try { + // const [initialEntities, subscription] = await sdk.subscribeEntityQuery({ + // historical: false, + // query: new ToriiQueryBuilder() + // .withClause(KeysClause([], [undefined], "VariableLen").build()) + // .addEntityModel("pixelaw-App") + // .includeHashedKeys(), + // callback(response: { data?: ToriiResponse; error?: Error }): void { + // if (response.data[0].entityId === "0x0") return + // console.log("jaja", response.data[0]) + // }, + // }) + + const query = + "SELECT internal_entity_id, name, system, action, icon FROM 'pixelaw-App';"; + + const response = await fetch(`${worldConfig.toriiUrl}/sql?query=${query}`); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + const json = await response.json(); + const apps = json.map((item) => { + return { + name: felt252ToString(item.name), + icon: felt252ToUnicode(item.icon), + action: felt252ToString(item.action), + system: item.system, + entity: { + id: item.internal_entity_id, + }, + }; + }); + const provider = new RpcProvider({ nodeUrl: worldConfig.rpcUrl }); + + const coreActionsAddress = await queryTorii( + worldConfig.toriiUrl, + `SELECT * FROM "pixelaw-CoreActionsAddress" LIMIT 1;`, - (rows: any[]) => { - return rows[0].value - }, - ) - const coreActionsClass = getClass(provider, coreActionsAddress) - - let contracts: SimpleContract[] = [ - { - kind: "DojoContract", - address: coreActionsAddress, - abi: coreActionsClass["abi"], - tag: "pixelaw-actions", - name: "", - }, - ] - - // TODO WE're now adding ALL apps to the list to be approved for sessions. - // Eventually we'll let the user add apps so this will change - contracts = contracts.concat(await Promise.all(apps.map((app) => getAbi(provider, app)))) - - const base = baseManifest(worldConfig.world) - - const manifest = { - ...base, - contracts, - } as unknown as Manifest - - return { apps, manifest, coreAddress: coreActionsAddress } - } catch (error) { - console.error("Error fetching apps and manifest:", error) - return { apps: [], manifest: {} as Manifest, coreAddress: "" } - } + (rows: any[]) => { + return rows[0].value; + }, + ); + const coreActionsClass = getClass(provider, coreActionsAddress); + + let contracts: SimpleContract[] = [ + { + kind: "DojoContract", + address: coreActionsAddress, + abi: coreActionsClass["abi"], + tag: "pixelaw-actions", + name: "", + }, + ]; + + // TODO WE're now adding ALL apps to the list to be approved for sessions. + // Eventually we'll let the user add apps so this will change + contracts = contracts.concat( + await Promise.all(apps.map((app) => getAbi(provider, app))), + ); + + const base = baseManifest(worldConfig.world); + + const manifest = { + ...base, + contracts, + } as unknown as Manifest; + + return { apps, manifest, coreAddress: coreActionsAddress }; + } catch (error) { + console.error("Error fetching apps and manifest:", error); + return { apps: [], manifest: {} as Manifest, coreAddress: "" }; + } } -function setupControllerConnector(manifest: Manifest, worldConfig: DojoConfig): ControllerConnector | null { - if (!worldConfig.wallets.controller) { - return null - } - const cacheKey = JSON.stringify({ manifest, rpcUrl: worldConfig.wallets.controller?.rpcUrl }) - if (controllerConnectorCache.has(cacheKey)) { - return controllerConnectorCache.get(cacheKey) || null - } +function setupControllerConnector( + manifest: Manifest, + worldConfig: DojoConfig, +): ControllerConnector | null { + if (!worldConfig.wallets.controller) { + return null; + } + const cacheKey = JSON.stringify({ + manifest, + rpcUrl: worldConfig.wallets.controller?.rpcUrl, + }); + if (controllerConnectorCache.has(cacheKey)) { + return controllerConnectorCache.get(cacheKey) || null; + } - const connector = worldConfig.wallets.controller - ? getControllerConnector({ - feeTokenAddress: worldConfig.feeTokenAddress, - manifest, - rpcUrl: worldConfig.wallets.controller.rpcUrl!, - }) - : null - // console.log("c", worldConfig) - controllerConnectorCache.set(cacheKey, connector) - return connector + const connector = worldConfig.wallets.controller + ? getControllerConnector({ + feeTokenAddress: worldConfig.feeTokenAddress, + manifest, + rpcUrl: worldConfig.wallets.controller.rpcUrl!, + }) + : null; + // console.log("c", worldConfig) + controllerConnectorCache.set(cacheKey, connector); + return connector; } async function setupBurnerConnector( - rpcProvider: DojoProvider, - worldConfig: DojoConfig, - storage: Storage, + rpcProvider: DojoProvider, + worldConfig: DojoConfig, + storage: Storage, ): Promise { - const cacheKey = JSON.stringify({ rpcProvider, burnerConfig: worldConfig.wallets?.burner }) - if (burnerConnectorCache.has(cacheKey)) { - return burnerConnectorCache.get(cacheKey) || null - } + const cacheKey = JSON.stringify({ + rpcProvider, + burnerConfig: worldConfig.wallets?.burner, + }); + if (burnerConnectorCache.has(cacheKey)) { + return burnerConnectorCache.get(cacheKey) || null; + } - // Load them in advance, needs to be async - const burnerCookies = await storage.getItem("burner") - - // TODO this document shim of cookie is for storage of burners using the Cookie library - if (typeof window === "undefined") { - // @ts-ignore don't care about implementing the other fields, its for nodejs - global.document = { - get cookie() { - return (burnerCookies as string) ?? "" - }, - set cookie(cookieStr) { - storage.setItem("burner", cookieStr).catch(console.error) - }, - } - } + // Load them in advance, needs to be async + const burnerCookies = await storage.getItem("burner"); + + // TODO this document shim of cookie is for storage of burners using the Cookie library + if (typeof window === "undefined") { + // @ts-ignore don't care about implementing the other fields, its for nodejs + global.document = { + get cookie() { + return (burnerCookies as string) ?? ""; + }, + set cookie(cookieStr) { + storage.setItem("burner", cookieStr).catch(console.error); + }, + }; + } + + const promise = (async () => { + if (worldConfig.wallets?.burner) { + const burnerConfig = worldConfig.wallets.burner; - const promise = (async () => { - if (worldConfig.wallets?.burner) { - const burnerConfig = worldConfig.wallets.burner - - const manager = new BurnerManager({ - ...burnerConfig, - feeTokenAddress: worldConfig.feeTokenAddress, - rpcProvider: rpcProvider.provider, - masterAccount: new Account( - rpcProvider.provider as never as ProviderInterface, - burnerConfig.masterAddress!, - burnerConfig.masterPrivateKey!, - ), - } as unknown as BurnerManagerOptions) - - await manager.init() - - if (manager.list().length === 0) { - try { - await manager.create() - } catch (e) { - console.error(e) - } - } - console.log("burn") - return new BurnerConnector( - { - id: "burner", - name: `burner_${formatAddress(manager.account!.address)}`, - }, - manager.account!, - ) + const manager = new BurnerManager({ + ...burnerConfig, + feeTokenAddress: worldConfig.feeTokenAddress, + rpcProvider: rpcProvider.provider, + masterAccount: new Account( + rpcProvider.provider as never as ProviderInterface, + burnerConfig.masterAddress!, + burnerConfig.masterPrivateKey!, + ), + } as unknown as BurnerManagerOptions); + + await manager.init(); + + if (manager.list().length === 0) { + try { + await manager.create(); + } catch (e) { + console.error(e); } - return null - })() + } + console.log("burn"); + return new BurnerConnector( + { + id: "burner", + name: `burner_${formatAddress(manager.account!.address)}`, + }, + manager.account!, + ); + } + return null; + })(); - burnerConnectorCache.set(cacheKey, promise) - return promise + burnerConnectorCache.set(cacheKey, promise); + return promise; } diff --git a/packages/core-dojo/src/DojoEngine.ts b/packages/core-dojo/src/DojoEngine.ts index cce42d8..237bc7b 100644 --- a/packages/core-dojo/src/DojoEngine.ts +++ b/packages/core-dojo/src/DojoEngine.ts @@ -1,130 +1,143 @@ import { - AblyUpdateService, - type Coordinate, - type Engine, - type Engines, - type EngineStatus, - type Interaction, - type Pixel, - type PixelawCore, - type QueueItem, -} from "@pixelaw/core" -import { Account, type ProviderInterface } from "starknet" -import { DojoAppStore } from "./DojoAppStore.ts" -import { dojoInit, type DojoStuff } from "./DojoEngine.init.ts" -import { DojoExecutor } from "./DojoExecutor.ts" -import { DojoInteraction } from "./DojoInteraction.ts" -import { DojoNotificationStore } from "./DojoNotificationStore.ts" -import { DojoQueueStore } from "./DojoQueueStore.ts" -import DojoSqlPixelStore from "./DojoSqlPixelStore.ts" -import { DojoWallet } from "./DojoWallet.ts" -import { type DojoConfig, ENGINE_ID } from "./types.ts" + AblyUpdateService, + type Coordinate, + type Engine, + type Engines, + type EngineStatus, + type Interaction, + type Pixel, + type PixelawCore, + type QueueItem, +} from "@pixelaw/core"; +import { Account, type ProviderInterface } from "starknet"; +import { DojoAppStore } from "./DojoAppStore.ts"; +import { dojoInit, type DojoStuff } from "./DojoEngine.init.ts"; +import { DojoExecutor } from "./DojoExecutor.ts"; +import { DojoInteraction } from "./DojoInteraction.ts"; +import { DojoNotificationStore } from "./DojoNotificationStore.ts"; +import { DojoQueueStore } from "./DojoQueueStore.ts"; +import DojoSqlPixelStore from "./DojoSqlPixelStore.ts"; +import { DojoWallet } from "./DojoWallet.ts"; +import { type DojoConfig, ENGINE_ID } from "./types.ts"; export class DojoEngine implements Engine { - id: Engines = ENGINE_ID - status: EngineStatus = "uninitialized" - config: DojoConfig = null! - dojoSetup: DojoStuff | null = null - core: PixelawCore - - constructor(core: PixelawCore) { - this.core = core + id: Engines = ENGINE_ID; + status: EngineStatus = "uninitialized"; + config: DojoConfig = null!; + dojoSetup: DojoStuff | null = null; + core: PixelawCore; + + constructor(core: PixelawCore) { + this.core = core; + } + + async init(config: DojoConfig) { + this.config = config; + try { + // Setup Dojo + this.dojoSetup = await dojoInit(this.config, this.core); + this.status = this.dojoSetup ? "ready" : "error"; + + // Setup AppStore + this.core.appStore = await DojoAppStore.getInstance(this.dojoSetup); + + // Setup PixelStore + this.core.pixelStore = await DojoSqlPixelStore.getInstance(this.core); + + // // Setup UpdateService + this.core.updateService = new AblyUpdateService(this.core); + + // Setup TileStore + // this.core.tileStore = new RestTileStore(config.serverUrl) + + this.core.executor = new DojoExecutor(this.core, this.dojoSetup.provider); + + this.core.queue = await DojoQueueStore.getInstance( + this.config.toriiUrl, + this.dojoSetup, + ); + + this.core.notification = await DojoNotificationStore.getInstance( + this.core, + ); + } catch (error) { + console.error("Dojo init error:", error); } - - async init(config: DojoConfig) { - this.config = config - try { - // Setup Dojo - this.dojoSetup = await dojoInit(this.config, this.core) - this.status = this.dojoSetup ? "ready" : "error" - - // Setup AppStore - this.core.appStore = await DojoAppStore.getInstance(this.dojoSetup) - - // Setup PixelStore - this.core.pixelStore = await DojoSqlPixelStore.getInstance(this.core) - - // // Setup UpdateService - this.core.updateService = new AblyUpdateService(this.core) - - // Setup TileStore - // this.core.tileStore = new RestTileStore(config.serverUrl) - - this.core.executor = new DojoExecutor(this.core, this.dojoSetup.provider) - - this.core.queue = await DojoQueueStore.getInstance(this.config.toriiUrl, this.dojoSetup) - - this.core.notification = await DojoNotificationStore.getInstance(this.core) - } catch (error) { - console.error("Dojo init error:", error) - } - } - - async prepInteraction(coordinate: Coordinate): Promise { - const pixel = this.core.pixelStore.getPixel(coordinate) ?? ({ x: coordinate[0], y: coordinate[1] } as Pixel) - const app = this.core.appStore.getByName(this.core.app) - const color = this.core.color - - const interaction: Interaction = await DojoInteraction.create(this, app, pixel, color) - - return interaction - } - - async executeQueueItem(item: QueueItem): Promise { - // const core_actions_address = this.dojoSetup.manifest.contracts.find((item) => item.tag === "pixelaw-actions") - console.log("RERR", this.dojoSetup.manifest.contracts) - const dojoCall = { - contractAddress: this.dojoSetup.coreAddress, //"0x06e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b", // TODO properly configure pixelaw_actions contract, - entrypoint: "process_queue", - calldata: [ - item.id, - item.timestamp, - item.called_system, - item.selector, - item.calldata.length, - ...item.calldata, - ], - } - - this.core.executor.enqueue(dojoCall, console.log, (e: Error) => { - let error = e.message - // console.error("Error executing DojoCall:", error) - - const regex = /Failure reason:\s*"([^"]+)"/ - const match = error.match(regex) - - if (match) { - const failureReason = match[1] - error = failureReason - } - this.core.events.emit("error", { coordinate: null, error }) - }) - - return true - } - - /* + } + + async prepInteraction(coordinate: Coordinate): Promise { + const pixel = + this.core.pixelStore.getPixel(coordinate) ?? + ({ x: coordinate[0], y: coordinate[1] } as Pixel); + const app = this.core.appStore.getByName(this.core.app); + const color = this.core.color; + + const interaction: Interaction = await DojoInteraction.create( + this, + app, + pixel, + color, + ); + + return interaction; + } + + async executeQueueItem(item: QueueItem): Promise { + // const core_actions_address = this.dojoSetup.manifest.contracts.find((item) => item.tag === "pixelaw-actions") + console.log("RERR", this.dojoSetup.manifest.contracts); + const dojoCall = { + contractAddress: this.dojoSetup.coreAddress, //"0x06e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b", // TODO properly configure pixelaw_actions contract, + entrypoint: "process_queue", + calldata: [ + item.id, + item.timestamp, + item.called_system, + item.selector, + item.calldata.length, + ...item.calldata, + ], + }; + + this.core.executor.enqueue(dojoCall, console.log, (e: Error) => { + let error = e.message; + // console.error("Error executing DojoCall:", error) + + const regex = /Failure reason:\s*"([^"]+)"/; + const match = error.match(regex); + + if (match) { + const failureReason = match[1]; + error = failureReason; + } + this.core.events.emit("error", { coordinate: null, error }); + }); + + return true; + } + + /* | Account address | 0x13d9ee239f33fea4f8785b9e3870ade909e20a9599ae7cd62c1c292b73af1b7 | Private key | 0x1c9053c053edf324aec366a34c6901b1095b07af69495bffec7d7fe21effb1b | Public key | 0x4c339f18b9d1b95b64a6d378abd1480b2e0d5d5bd33cd0828cbce4d65c27284 */ - async getPreDeployedWallet(privateKey: string): Promise { - // privateKey = "0x1c9053c053edf324aec366a34c6901b1095b07af69495bffec7d7fe21effb1b" - // const deployer = "0x41a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf" - // - // const publicKey = ec.starkCurve.getStarkKey(privateKey) - // - // TODO for now just hardcoding the 2nd predeployed from dev katana - const address = "0x13d9ee239f33fea4f8785b9e3870ade909e20a9599ae7cd62c1c292b73af1b7" - const account = new Account( - this.dojoSetup.provider.provider as never as ProviderInterface, - address, - privateKey, - "1", - "0x3", - ) - const chainId = await this.dojoSetup.provider.provider.getChainId() - return new DojoWallet("predeployed", chainId, account) - } + async getPreDeployedWallet(privateKey: string): Promise { + // privateKey = "0x1c9053c053edf324aec366a34c6901b1095b07af69495bffec7d7fe21effb1b" + // const deployer = "0x41a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf" + // + // const publicKey = ec.starkCurve.getStarkKey(privateKey) + // + // TODO for now just hardcoding the 2nd predeployed from dev katana + const address = + "0x13d9ee239f33fea4f8785b9e3870ade909e20a9599ae7cd62c1c292b73af1b7"; + const account = new Account( + this.dojoSetup.provider.provider as never as ProviderInterface, + address, + privateKey, + "1", + "0x3", + ); + const chainId = await this.dojoSetup.provider.provider.getChainId(); + return new DojoWallet("predeployed", chainId, account); + } } diff --git a/packages/core-dojo/src/DojoExecutor.ts b/packages/core-dojo/src/DojoExecutor.ts index 580d3ff..e6937b8 100644 --- a/packages/core-dojo/src/DojoExecutor.ts +++ b/packages/core-dojo/src/DojoExecutor.ts @@ -1,114 +1,173 @@ -import { type DojoCall, type DojoProvider, parseDojoCall } from "@dojoengine/core" -import { type Coordinate, type Executor, NAMESPACE, type PixelawCore, parsePixelError } from "@pixelaw/core" -import type { AccountInterface, BigNumberish, UniversalDetails } from "starknet" -import type { DojoWallet } from "./DojoWallet.ts" -import { parseEventsFromSimulation } from "./utils/parseEvents.ts" +import { + type DojoCall, + type DojoProvider, + parseDojoCall, +} from "@dojoengine/core"; +import { + type Coordinate, + type Executor, + NAMESPACE, + type PixelawCore, + parsePixelError, +} from "@pixelaw/core"; +import type { + AccountInterface, + BigNumberish, + UniversalDetails, +} from "starknet"; +import type { DojoWallet } from "./DojoWallet.ts"; +import { parseEventsFromSimulation } from "./utils/parseEvents.ts"; interface ExecutionTask { - dojoCall: DojoCall - onSuccess: (result: unknown) => void - onFail: (error: unknown) => void + dojoCall: DojoCall; + onSuccess: (result: unknown) => void; + onFail: (error: unknown) => void; } // I don't know if we need a special queueing system for this, but after some troubles // decided to put one in. This would also be the place for more advanced nonce mangement // if we need it export class DojoExecutor implements Executor { - private core: PixelawCore - private queue: ExecutionTask[] = [] - private executing = false - private provider: DojoProvider - private _wallet: DojoWallet - private _nonce: BigNumberish = "0x9" - private _account: AccountInterface - - constructor(core: PixelawCore, provider: DojoProvider) { - this.core = core - this.provider = provider - // this.account = wallet.getAccount() as OptimisticNonceAccount - // this.account.execute = optimisticExecute.bind(this.account) - - // this.provider.provider.getNonceForAddress(account.address, "latest") + private core: PixelawCore; + private queue: ExecutionTask[] = []; + private executing = false; + private provider: DojoProvider; + private _wallet: DojoWallet; + private _nonce: BigNumberish = "0x9"; + private _account: AccountInterface; + + constructor(core: PixelawCore, provider: DojoProvider) { + this.core = core; + this.provider = provider; + // this.account = wallet.getAccount() as OptimisticNonceAccount + // this.account.execute = optimisticExecute.bind(this.account) + + // this.provider.provider.getNonceForAddress(account.address, "latest") + } + + public set account(newAccount: AccountInterface) { + this._account = newAccount; + // this.syncNonce().then(() => { + // // let it finish + // }) + } + + public get pendingCalls(): number { + return this.queue.length; + } + + public enqueue( + dojoCall: DojoCall, + onSuccess: (result: unknown) => void, + onFail: (error: unknown) => void, + ): void { + this.queue.push({ dojoCall, onSuccess, onFail }); + this.processQueue(); + } + + public set wallet(wallet: DojoWallet) { + this._wallet = wallet; + } + + private async syncNonce() { + this._nonce = await this._account.getNonce(); + console.log("aa", this._nonce); + } + + private async processQueue(): Promise { + if (this.executing || this.queue.length === 0) { + console.log( + "DojoExecutor: Skipping processQueue - executing:", + this.executing, + "queue empty:", + this.queue.length === 0, + ); + return; } - public set account(newAccount: AccountInterface) { - this._account = newAccount - // this.syncNonce().then(() => { - // // let it finish - // }) + if (!this.core.account) { + console.log("DojoExecutor: account not loaded"); + return; } - public get pendingCalls(): number { - return this.queue.length + // Determine the correct way to access the account + let account: AccountInterface | null = null; + + if (this.core.account.walletProvider?.account) { + // Legacy path - account is nested in walletProvider + account = this.core.account.walletProvider.account as AccountInterface; + console.log("DojoExecutor: Using walletProvider.account path"); + } else if (this.core.account.account) { + // DojoWallet path - account is in .account property + account = this.core.account.account as AccountInterface; + console.log("DojoExecutor: Using DojoWallet.account path"); + } else if (this.core.account.address && this.core.account.execute) { + // Direct AccountInterface - core.account is the account itself + account = this.core.account as AccountInterface; } - public enqueue(dojoCall: DojoCall, onSuccess: (result: unknown) => void, onFail: (error: unknown) => void): void { - this.queue.push({ dojoCall, onSuccess, onFail }) - this.processQueue() + if (!account) { + console.log( + "DojoExecutor: Could not find account in any expected location", + ); + return; } - public set wallet(wallet: DojoWallet) { - this._wallet = wallet - } - - private async syncNonce() { - this._nonce = await this._account.getNonce() - console.log("aa", this._nonce) - } - - private async processQueue(): Promise { - if (this.executing || this.queue.length === 0) return - if (!this.core.account) { - console.log("account not loaded") - return - } - - this.executing = true - const task = this.queue.shift()! - const account = this.core.account.walletProvider.account as AccountInterface - console.log("processQueue") - - try { - const options: UniversalDetails = { - version: 3, - blockIdentifier: "pending", - nonce: this._nonce, - } - - const call = parseDojoCall(this.provider.manifest, NAMESPACE, task.dojoCall) - - console.log(account) - - const [sim, { transaction_hash }] = await Promise.all([ - account.simulateTransaction( - [ - { - ...call, - type: "INVOKE_FUNCTION", - }, - ], - options, - ), - account.execute([call], options), - ]) - console.log("s", sim[0], transaction_hash) - - // FIXME somehow the actual simulation output is not conforming the types - if (sim[0].transaction_trace?.execute_invocation?.revert_reason) { - const error = sim[0].transaction_trace.execute_invocation.revert_reason - - console.log("fail", error) - task.onFail(error) - } else { - // TODO parse the sim for all the pixel changes and apply those to the PixelStore - const pixels = parseEventsFromSimulation( - "0x7e607b2fbb4cfb3fb9d1258fa2ff3aa94f17b3820e42bf1e6a43e2de3f5772e", - sim, - ) - - this.core.updateService["channel"].publish("PixelUpdate", JSON.stringify(pixels)) - - /* + this.executing = true; + const task = this.queue.shift()!; + try { + // Get the current nonce from the account instead of using cached nonce + const currentNonce = await account.getNonce(); + // Update our cached nonce + this._nonce = currentNonce; + + const options: UniversalDetails = { + version: 3, + blockIdentifier: "pending", + nonce: currentNonce, + }; + + const call = parseDojoCall( + this.provider.manifest, + NAMESPACE, + task.dojoCall, + ); + + console.log(account); + + const [sim, { transaction_hash }] = await Promise.all([ + account.simulateTransaction( + [ + { + ...call, + type: "INVOKE_FUNCTION", + }, + ], + options, + ), + account.execute([call], options), + ]); + console.log("s", sim[0], transaction_hash); + + // FIXME somehow the actual simulation output is not conforming the types + if (sim[0].transaction_trace?.execute_invocation?.revert_reason) { + const error = sim[0].transaction_trace.execute_invocation.revert_reason; + + console.log("fail", error); + task.onFail(error); + } else { + // TODO parse the sim for all the pixel changes and apply those to the PixelStore + const pixels = parseEventsFromSimulation( + "0x7e607b2fbb4cfb3fb9d1258fa2ff3aa94f17b3820e42bf1e6a43e2de3f5772e", + sim, + ); + + this.core.updateService["channel"].publish( + "PixelUpdate", + JSON.stringify(pixels), + ); + + /* // TODO properly implement the messaging using DOJO. // Right now it's changing drastically so i used Ably just to get it working. // @@ -147,17 +206,17 @@ export class DojoExecutor implements Executor { } console.info(msg) */ - task.onSuccess(sim[0]) - } - } catch (error) { - console.log(error) - task.onFail(error) - } finally { - console.log("done!") - this.executing = false - this.processQueue() - } + task.onSuccess(sim[0]); + } + } catch (error) { + console.log(error); + task.onFail(error); + } finally { + console.log("done!"); + this.executing = false; + this.processQueue(); } + } } /* @@ -167,14 +226,14 @@ Execution failed. Failure reason: (0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x0 (''), "10_12 Another player is here", 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')). */ function parseError(error: string): { coordinate: Coordinate; error: string } { - const regex = /"\s*(\d+_\d+\s[^"]+)\s*"/ - const match = error.match(regex) + const regex = /"\s*(\d+_\d+\s[^"]+)\s*"/; + const match = error.match(regex); - if (!match) { - return { coordinate: null, error } - } + if (!match) { + return { coordinate: null, error }; + } - const failureReason = match[1] - const pixelError = parsePixelError(failureReason) - return pixelError + const failureReason = match[1]; + const pixelError = parsePixelError(failureReason); + return pixelError; } diff --git a/packages/core-dojo/src/DojoNotificationStore.ts b/packages/core-dojo/src/DojoNotificationStore.ts index 8c4f41b..63e25c0 100644 --- a/packages/core-dojo/src/DojoNotificationStore.ts +++ b/packages/core-dojo/src/DojoNotificationStore.ts @@ -1,163 +1,183 @@ -import { KeysClause, type SDK } from "@dojoengine/sdk" -import { queryTorii } from "@dojoengine/sdk/sql" +import { KeysClause, type SDK } from "@dojoengine/sdk"; +import { queryTorii } from "@dojoengine/sdk/sql"; import { - type Bounds, - type Coordinate, - MAX_DIMENSION, - type Notification, - type NotificationStore, - type NotificationStoreEvents, - type PixelawCore, - type Position, - QueueStore, - areBoundsEqual, -} from "@pixelaw/core" -import mitt from "mitt" -import type { DojoStuff } from "./DojoEngine.init.ts" -import type { DojoEngine } from "./DojoEngine.ts" -import type { SchemaType } from "./generated/models.gen.ts" -import { getQueryBounds } from "./utils/querybuilder.ts" -import { convertFullHexString } from "./utils/utils.ts" - -type State = { [key: string]: Notification | undefined } - -const QUERY_RADIUS = 20 + type Bounds, + type Coordinate, + MAX_DIMENSION, + type Notification, + type NotificationStore, + type NotificationStoreEvents, + type PixelawCore, + type Position, + QueueStore, + areBoundsEqual, +} from "@pixelaw/core"; +import mitt from "mitt"; +import type { DojoStuff } from "./DojoEngine.init.ts"; +import type { DojoEngine } from "./DojoEngine.ts"; +import type { SchemaType } from "./generated/models.gen.ts"; +import { getQueryBounds } from "./utils/querybuilder.ts"; +import { convertFullHexString } from "./utils/utils.ts"; + +type State = { [key: string]: Notification | undefined }; + +const QUERY_RADIUS = 20; export class DojoNotificationStore implements NotificationStore { - public readonly eventEmitter = mitt() - private dojoStuff - private sdk: SDK - private static instance: DojoNotificationStore - private isSubscribed = false - private cacheUpdated: number = Date.now() - private state: State = {} - private toriiUrl - private queryBounds: Bounds | null = null - private core: PixelawCore - - protected constructor(core: PixelawCore) { - const engine = core._engine as DojoEngine - this.sdk = engine.dojoSetup.sdk - this.toriiUrl = engine.dojoSetup.toriiUrl - this.core = core + public readonly eventEmitter = mitt(); + private dojoStuff; + private sdk: SDK; + private static instance: DojoNotificationStore; + private isSubscribed = false; + private cacheUpdated: number = Date.now(); + private state: State = {}; + private toriiUrl; + private queryBounds: Bounds | null = null; + private core: PixelawCore; + + protected constructor(core: PixelawCore) { + const engine = core._engine as DojoEngine; + this.sdk = engine.dojoSetup.sdk; + this.toriiUrl = engine.dojoSetup.toriiUrl; + this.core = core; + } + + // Singleton factory + public static async getInstance( + core: PixelawCore, + ): Promise { + if (!DojoNotificationStore.instance) { + DojoNotificationStore.instance = new DojoNotificationStore(core); + + await DojoNotificationStore.instance.subscribe(); + await DojoNotificationStore.instance.initialize(); } - - // Singleton factory - public static async getInstance(core: PixelawCore): Promise { - if (!DojoNotificationStore.instance) { - DojoNotificationStore.instance = new DojoNotificationStore(core) - - await DojoNotificationStore.instance.subscribe() - await DojoNotificationStore.instance.initialize() - } - return DojoNotificationStore.instance + return DojoNotificationStore.instance; + } + + private async initialize() { + try { + // TODO Notifications should be filtered by wallet address + // const wallet = this.core.wallet as DojoWallet + + const items = await queryTorii( + this.toriiUrl, + createSqlQueryByRadius( + this.core.center, + QUERY_RADIUS, + this.core.lastNotification /*, wallet.getAccount()*/, + ), + (rows: any[]) => { + return rows.map((item) => { + // const item = JSON.parse(str.d) + return { + ...item, + message: convertFullHexString(item.text), + } as Notification; + }); + }, + ); + for (const item of items) { + // this.setNotification(item.id, item) + this.eventEmitter.emit("added", item); + } + } catch (e) { + console.error(e); } - - private async initialize() { - try { - // TODO Notifications should be filtered by wallet address - // const wallet = this.core.wallet as DojoWallet - - const items = await queryTorii( - this.toriiUrl, - createSqlQueryByRadius( - this.core.center, - QUERY_RADIUS, - this.core.lastNotification /*, wallet.getAccount()*/, - ), - (rows: any[]) => { - return rows.map((item) => { - // const item = JSON.parse(str.d) - return { - ...item, - message: convertFullHexString(item.text), - } as Notification - }) - }, - ) - for (const item of items) { - // this.setNotification(item.id, item) - this.eventEmitter.emit("added", item) - } - } catch (e) { - console.error(e) - } + } + + private async subscribe() { + if (this.isSubscribed) return; + try { + const subscription = this.sdk.client.onEventMessageUpdated( + KeysClause( + ["pixelaw-Notification"], + [undefined], + "VariableLen", + ).build(), + (id, data) => { + if (id === "0x0") return; + try { + const item = data["models"]["pixelaw-Notification"]; + console.log("notification from sub", item); + const notification: Notification = { + from: + item.from.value.option === "None" + ? null + : item.from.value.value.value, + to: + item.to.value.option === "None" + ? null + : item.to.value.value.value, + color: item.color.value, + app: item.app.value, // TODO + position: { + x: item.position.value.x.value, + y: item.position.value.y.value, + }, + text: convertFullHexString(item.text.value), + }; + // console.log("notification", notification) + // TODO decide if we store the Notification or not + // this.setNotification(item.id.value, notification) + this.core.events.emit("notification", notification); + } catch (e) { + console.error(e); + } + + this.cacheUpdated = Date.now(); + }, + ); + + this.isSubscribed = true; + return () => { + console.log("cancel"); + subscription.cancel(); + this.isSubscribed = false; + }; + } catch (error) { + console.error("Subscription error:", error); } + } - private async subscribe() { - if (this.isSubscribed) return - try { - const subscription = this.sdk.client.onEventMessageUpdated( - KeysClause(["pixelaw-Notification"], [undefined], "VariableLen").build(), - (id, data) => { - if (id === "0x0") return - try { - const item = data["models"]["pixelaw-Notification"] - console.log("notification from sub", item) - const notification: Notification = { - from: item.from.value.option === "None" ? null : item.from.value.value.value, - to: item.to.value.option === "None" ? null : item.to.value.value.value, - color: item.color.value, - app: item.app.value, // TODO - position: { - x: item.position.value.x.value, - y: item.position.value.y.value, - }, - text: convertFullHexString(item.text.value), - } - // console.log("notification", notification) - // TODO decide if we store the Notification or not - // this.setNotification(item.id.value, notification) - this.core.events.emit("notification", notification) - } catch (e) { - console.error(e) - } - - this.cacheUpdated = Date.now() - }, - ) - - this.isSubscribed = true - return () => { - console.log("cancel") - subscription.cancel() - this.isSubscribed = false - } - } catch (error) { - console.error("Subscription error:", error) - } - } + public setNotification(key: string, Notification: Notification): void { + this.state[key] = Notification; + } - public setNotification(key: string, Notification: Notification): void { - this.state[key] = Notification - } + getAll(): Notification[] { + return this.dojoStuff!.apps; + } - getAll(): Notification[] { - return this.dojoStuff!.apps - } + public setBounds(newBounds: Bounds): void { + const newQueryBounds = getQueryBounds(newBounds); - public setBounds(newBounds: Bounds): void { - const newQueryBounds = getQueryBounds(newBounds) - - if (!this.queryBounds || !areBoundsEqual(this.queryBounds, newQueryBounds)) { - this.queryBounds = newQueryBounds - } + if ( + !this.queryBounds || + !areBoundsEqual(this.queryBounds, newQueryBounds) + ) { + this.queryBounds = newQueryBounds; } + } - getLastForPosition(position: Position): Notification[] { - return [] - } + getLastForPosition(position: Position): Notification[] { + return []; + } } -export function createSqlQueryByRadius(center: Coordinate, radius: number, lastNotification: number, address: string) { - console.log("add", address) - let result = `SELECT +export function createSqlQueryByRadius( + center: Coordinate, + radius: number, + lastNotification: number, + address: string, +) { + console.log("add", address); + let result = `SELECT "from", "to", "text", "position.x" , "position.y", "color" FROM "pixelaw-Notification" WHERE (("position.x" - ${center[0]}) * ("position.x" - ${center[0]}) + ("position.y" - ${center[1]}) * ("position.y" - ${center[1]})) <= (${radius} * ${radius}) - ` + `; - result += ";" - return result + result += ";"; + return result; } /* diff --git a/packages/core-dojo/src/DojoQueueStore.ts b/packages/core-dojo/src/DojoQueueStore.ts index 70020bf..feeb745 100644 --- a/packages/core-dojo/src/DojoQueueStore.ts +++ b/packages/core-dojo/src/DojoQueueStore.ts @@ -1,109 +1,116 @@ -import { KeysClause, type SDK } from "@dojoengine/sdk" -import { queryTorii } from "@dojoengine/sdk/sql" -import type { QueueItem, QueueStore, QueueStoreEvents } from "@pixelaw/core" -import mitt from "mitt" -import type { DojoStuff } from "./DojoEngine.init.ts" -import type { SchemaType } from "./generated/models.gen.ts" +import { KeysClause, type SDK } from "@dojoengine/sdk"; +import { queryTorii } from "@dojoengine/sdk/sql"; +import type { QueueItem, QueueStore, QueueStoreEvents } from "@pixelaw/core"; +import mitt from "mitt"; +import type { DojoStuff } from "./DojoEngine.init.ts"; +import type { SchemaType } from "./generated/models.gen.ts"; -type State = { [key: string]: QueueItem | undefined } +type State = { [key: string]: QueueItem | undefined }; export class DojoQueueStore implements QueueStore { - public readonly eventEmitter = mitt() - private dojoStuff - private sdk: SDK - private static instance: DojoQueueStore - private isSubscribed = false - private cacheUpdated: number = Date.now() - private state: State = {} - private toriiUrl + public readonly eventEmitter = mitt(); + private dojoStuff; + private sdk: SDK; + private static instance: DojoQueueStore; + private isSubscribed = false; + private cacheUpdated: number = Date.now(); + private state: State = {}; + private toriiUrl; - protected constructor(toriiUrl: string, dojoStuff: DojoStuff) { - this.dojoStuff = dojoStuff - this.sdk = dojoStuff.sdk - this.toriiUrl = toriiUrl - } + protected constructor(toriiUrl: string, dojoStuff: DojoStuff) { + this.dojoStuff = dojoStuff; + this.sdk = dojoStuff.sdk; + this.toriiUrl = toriiUrl; + } - // Singleton factory - public static async getInstance(toriiUrl: string, dojoStuff: DojoStuff): Promise { - if (!DojoQueueStore.instance) { - DojoQueueStore.instance = new DojoQueueStore(toriiUrl, dojoStuff) + // Singleton factory + public static async getInstance( + toriiUrl: string, + dojoStuff: DojoStuff, + ): Promise { + if (!DojoQueueStore.instance) { + DojoQueueStore.instance = new DojoQueueStore(toriiUrl, dojoStuff); - await DojoQueueStore.instance.subscribe() - await DojoQueueStore.instance.retrieve() - } - return DojoQueueStore.instance + await DojoQueueStore.instance.subscribe(); + await DojoQueueStore.instance.retrieve(); } + return DojoQueueStore.instance; + } - public async retrieve(): Promise { - try { - const items = await queryTorii( - this.toriiUrl, - `SELECT qs.id, qs.timestamp, qs.called_system, qs.selector, qs.calldata + public async retrieve(): Promise { + try { + const items = await queryTorii( + this.toriiUrl, + `SELECT qs.id, qs.timestamp, qs.called_system, qs.selector, qs.calldata FROM "pixelaw-QueueScheduled" qs INNER JOIN "pixelaw-QueueItem" qi ON qi.id = qs.id; `, - (rows: any[]) => { - return rows.map((item) => { - return { - ...item, - calldata: JSON.parse(item.calldata), - } - }) - }, - ) - for (const item of items) { - console.log("scheduling") - this.setQueueItem(item.id, item) - this.eventEmitter.emit("scheduled", item) - } - // console.log({ items }) - } catch (e) { - console.error(e) - } + (rows: any[]) => { + return rows.map((item) => { + return { + ...item, + calldata: JSON.parse(item.calldata), + }; + }); + }, + ); + for (const item of items) { + console.log("scheduling"); + this.setQueueItem(item.id, item); + this.eventEmitter.emit("scheduled", item); + } + // console.log({ items }) + } catch (e) { + console.error(e); } + } - private async subscribe() { - if (this.isSubscribed) return - try { - const subscription = this.sdk.client.onEventMessageUpdated( - KeysClause(["pixelaw-QueueScheduled"], [undefined], "VariableLen").build(), - (id, data) => { - if (id === "0x0") return - try { - const item = data["models"]["pixelaw-QueueScheduled"] + private async subscribe() { + if (this.isSubscribed) return; + try { + const subscription = this.sdk.client.onEventMessageUpdated( + KeysClause( + ["pixelaw-QueueScheduled"], + [undefined], + "VariableLen", + ).build(), + (id, data) => { + if (id === "0x0") return; + try { + const item = data["models"]["pixelaw-QueueScheduled"]; - const queueItem: QueueItem = { - calldata: item.calldata.value.map((val) => val.value), - called_system: item.called_system.value, - id: item.id.value, - selector: item.selector.value, - timestamp: item.timestamp.value, - } - this.setQueueItem(item.id.value, queueItem) - this.eventEmitter.emit("scheduled", queueItem) - } catch (e) { - console.error(e) - } + const queueItem: QueueItem = { + calldata: item.calldata.value.map((val) => val.value), + called_system: item.called_system.value, + id: item.id.value, + selector: item.selector.value, + timestamp: item.timestamp.value, + }; + this.setQueueItem(item.id.value, queueItem); + this.eventEmitter.emit("scheduled", queueItem); + } catch (e) { + console.error(e); + } - this.cacheUpdated = Date.now() - }, - ) + this.cacheUpdated = Date.now(); + }, + ); - this.isSubscribed = true - return () => { - console.log("cancel") - subscription.cancel() - this.isSubscribed = false - } - } catch (error) { - console.error("Subscription error:", error) - } - } - public setQueueItem(key: string, queueItem: QueueItem): void { - this.state[key] = queueItem - } - getAll(): QueueItem[] { - return this.dojoStuff!.apps + this.isSubscribed = true; + return () => { + console.log("cancel"); + subscription.cancel(); + this.isSubscribed = false; + }; + } catch (error) { + console.error("Subscription error:", error); } + } + public setQueueItem(key: string, queueItem: QueueItem): void { + this.state[key] = queueItem; + } + getAll(): QueueItem[] { + return this.dojoStuff!.apps; + } } diff --git a/packages/core-dojo/src/DojoSqlPixelStore.ts b/packages/core-dojo/src/DojoSqlPixelStore.ts index f13c7a9..6bccbad 100644 --- a/packages/core-dojo/src/DojoSqlPixelStore.ts +++ b/packages/core-dojo/src/DojoSqlPixelStore.ts @@ -1,239 +1,254 @@ -import { KeysClause, type SDK } from "@dojoengine/sdk" +import { KeysClause, type SDK } from "@dojoengine/sdk"; import { - type Bounds, - type Coordinate, - type Pixel, - type PixelStore, - type PixelStoreEvents, - type PixelawCore, - areBoundsEqual, - makeString, -} from "@pixelaw/core" -import mitt from "mitt" -import type { DojoEngine } from "./DojoEngine.ts" -import type { SchemaType } from "./generated/models.gen.ts" -import { getQueryBounds } from "./utils/querybuilder.ts" -import { convertFullHexString } from "./utils/utils.ts" - -type State = { [key: string]: Pixel | undefined } + type Bounds, + type Coordinate, + type Pixel, + type PixelStore, + type PixelStoreEvents, + type PixelawCore, + areBoundsEqual, + makeString, +} from "@pixelaw/core"; +import mitt from "mitt"; +import type { DojoEngine } from "./DojoEngine.ts"; +import type { SchemaType } from "./generated/models.gen.ts"; +import { getQueryBounds } from "./utils/querybuilder.ts"; +import { convertFullHexString } from "./utils/utils.ts"; + +type State = { [key: string]: Pixel | undefined }; class DojoSqlPixelStore implements PixelStore { - public readonly eventEmitter = mitt() - private static instance: DojoSqlPixelStore - private state: State = {} - private idLookupTable: Record = {} - private queryBounds: Bounds | null = null - private cacheUpdated: number = Date.now() - private isSubscribed = false - private sdk: SDK - private worker: any - private toriiUrl: string - private core: PixelawCore - - protected constructor(core: PixelawCore) { - const engine = core._engine as DojoEngine - this.sdk = engine.dojoSetup.sdk - this.toriiUrl = engine.dojoSetup.toriiUrl - this.core = core - - this.core.events.on("boundsChanged", (newBounds: Bounds) => { - this.prepare(newBounds) - }) + public readonly eventEmitter = mitt(); + private static instance: DojoSqlPixelStore; + private state: State = {}; + private idLookupTable: Record = {}; + private queryBounds: Bounds | null = null; + private cacheUpdated: number = Date.now(); + private isSubscribed = false; + private sdk: SDK; + private worker: any; + private toriiUrl: string; + private core: PixelawCore; + + protected constructor(core: PixelawCore) { + const engine = core._engine as DojoEngine; + this.sdk = engine.dojoSetup.sdk; + this.toriiUrl = engine.dojoSetup.toriiUrl; + this.core = core; + + this.core.events.on("boundsChanged", (newBounds: Bounds) => { + this.prepare(newBounds); + }); + } + + public static async getInstance( + core: PixelawCore, + ): Promise { + if (!DojoSqlPixelStore.instance) { + DojoSqlPixelStore.instance = new DojoSqlPixelStore(core); + const engine = core._engine as DojoEngine; + + if (typeof window !== "undefined" && Object.keys(window).length !== 0) { + // Browser environment + const workerUrl = new URL( + "./DojoSqlPixelStore.webworker.js", + import.meta.url, + ); + DojoSqlPixelStore.instance.worker = new Worker(workerUrl, { + type: "module", + }); + console.log("worker"); + } else { + // Node.js environment + const { Worker } = await import("node:worker_threads"); + const { fileURLToPath } = await import("node:url"); + const path = await import("node:path"); + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const workerPath = path.resolve( + __dirname, + "./DojoSqlPixelStore.webworker.js", + ); + DojoSqlPixelStore.instance.worker = new Worker(workerPath, { + workerData: { toriiUrl: engine.dojoSetup.toriiUrl }, + }); + } + + DojoSqlPixelStore.instance.worker.onmessage = + DojoSqlPixelStore.instance.handleRefreshWorker.bind( + DojoSqlPixelStore.instance, + ); + DojoSqlPixelStore.instance.subscribe(); } - - public static async getInstance(core: PixelawCore): Promise { - if (!DojoSqlPixelStore.instance) { - DojoSqlPixelStore.instance = new DojoSqlPixelStore(core) - const engine = core._engine as DojoEngine - - if (typeof window !== "undefined" && Object.keys(window).length !== 0) { - // Browser environment - const workerUrl = new URL("./DojoSqlPixelStore.webworker.js", import.meta.url) - DojoSqlPixelStore.instance.worker = new Worker(workerUrl, { type: "module" }) - console.log("worker") + return DojoSqlPixelStore.instance; + } + + private async subscribe() { + if (this.isSubscribed) return; + + try { + const subscription = this.sdk.client.onEntityUpdated( + KeysClause(["pixelaw-Pixel"], [undefined], "VariableLen").build(), + (data) => { + try { + console.log("pixel from sub", data); + const p = data["models"]["pixelaw-Pixel"]; + const id = data["hashed_keys"]; + if (Object.keys(data).length === 0) { + // Pixel got deleted + this.deletePixel(this.idLookupTable[id]); + delete this.idLookupTable[id]; } else { - // Node.js environment - const { Worker } = await import("node:worker_threads") - const { fileURLToPath } = await import("node:url") - const path = await import("node:path") - const __filename = fileURLToPath(import.meta.url) - const __dirname = path.dirname(__filename) - const workerPath = path.resolve(__dirname, "./DojoSqlPixelStore.webworker.js") - DojoSqlPixelStore.instance.worker = new Worker(workerPath, { - workerData: { toriiUrl: engine.dojoSetup.toriiUrl }, - }) - } - - DojoSqlPixelStore.instance.worker.onmessage = DojoSqlPixelStore.instance.handleRefreshWorker.bind( - DojoSqlPixelStore.instance, - ) - DojoSqlPixelStore.instance.subscribe() - } - return DojoSqlPixelStore.instance - } - - private async subscribe() { - if (this.isSubscribed) return - - try { - const subscription = this.sdk.client.onEntityUpdated( - KeysClause(["pixelaw-Pixel"], [undefined], "VariableLen").build(), - (data) => { - try { - console.log("pixel from sub", data) - const p = data["models"]["pixelaw-Pixel"] - const id = data["hashed_keys"] - if (Object.keys(data).length === 0) { - // Pixel got deleted - this.deletePixel(this.idLookupTable[id]) - delete this.idLookupTable[id] - } else { - const app = - p.app.value !== "0x0000000000000000000000000000000000000000000000000000000000000000" - ? this.core.appStore.getBySystem(p.app.value).name - : "" - const pixel: Pixel = { - action: convertFullHexString(p.action.value), - color: p.color.value, - owner: "", - text: convertFullHexString(p.text.value), - timestamp: p.timestamp.value, - app, - x: p.position.value.x.value, - y: p.position.value.y.value, - } - - const key = `${pixel.x}_${pixel.y}` - this.idLookupTable[id] = key - this.setPixel(key, pixel) - } - } catch (e) { - console.error(e) - } - - this.eventEmitter.emit("cacheUpdated", Date.now()) - this.cacheUpdated = Date.now() - }, - ) - - this.isSubscribed = true - return () => { - console.log("subcancel") - subscription.cancel() - this.isSubscribed = false + const app = + p.app.value !== + "0x0000000000000000000000000000000000000000000000000000000000000000" + ? this.core.appStore.getBySystem(p.app.value).name + : ""; + const pixel: Pixel = { + action: convertFullHexString(p.action.value), + color: p.color.value, + owner: "", + text: convertFullHexString(p.text.value), + timestamp: p.timestamp.value, + app, + x: p.position.value.x.value, + y: p.position.value.y.value, + }; + + const key = `${pixel.x}_${pixel.y}`; + this.idLookupTable[id] = key; + this.setPixel(key, pixel); } - } catch (error) { - console.error("Subscription error:", error) - } + } catch (e) { + console.error(e); + } + + this.eventEmitter.emit("cacheUpdated", Date.now()); + this.cacheUpdated = Date.now(); + }, + ); + + this.isSubscribed = true; + return () => { + console.log("subcancel"); + subscription.cancel(); + this.isSubscribed = false; + }; + } catch (error) { + console.error("Subscription error:", error); } + } - private handleRefreshWorker(event: MessageEvent) { - const { success, data, error } = event.data - if (success) { - this.state = { ...this.state, ...data } + private handleRefreshWorker(event: MessageEvent) { + const { success, data, error } = event.data; + if (success) { + this.state = { ...this.state, ...data }; - this.eventEmitter.emit("cacheUpdated", Date.now()) - } else { - console.error("RefreshWorker error:", error) - } + this.eventEmitter.emit("cacheUpdated", Date.now()); + } else { + console.error("RefreshWorker error:", error); } + } - public refresh(): void { - if (!this.queryBounds) return - const q = createSqlQuery(this.queryBounds) - - const query = encodeURIComponent(q) - - this.worker.postMessage({ query, toriiUrl: this.toriiUrl }) - } + public refresh(): void { + if (!this.queryBounds) return; + const q = createSqlQuery(this.queryBounds); - // public applyOptimisticState(pixel: Pixel): void { - // const newQueryBounds = getQueryBounds(newBounds) + const query = encodeURIComponent(q); - // if (!this.queryBounds || !areBoundsEqual(this.queryBounds, newQueryBounds)) { - // this.queryBounds = newQueryBounds - // this.refresh() - // } - // } + this.worker.postMessage({ query, toriiUrl: this.toriiUrl }); + } - public prepare(newBounds: Bounds): void { - const newQueryBounds = getQueryBounds(newBounds) + // public applyOptimisticState(pixel: Pixel): void { + // const newQueryBounds = getQueryBounds(newBounds) - if (!this.queryBounds || !areBoundsEqual(this.queryBounds, newQueryBounds)) { - this.queryBounds = newQueryBounds - this.refresh() - } - } + // if (!this.queryBounds || !areBoundsEqual(this.queryBounds, newQueryBounds)) { + // this.queryBounds = newQueryBounds + // this.refresh() + // } + // } - public getPixel(coord: Coordinate): Pixel | undefined { - const key = `${coord[0]}_${coord[1]}` - return this.state[key] - } + public prepare(newBounds: Bounds): void { + const newQueryBounds = getQueryBounds(newBounds); - public deletePixel(key: string): void { - delete this.state[key] + if ( + !this.queryBounds || + !areBoundsEqual(this.queryBounds, newQueryBounds) + ) { + this.queryBounds = newQueryBounds; + this.refresh(); } - - public setPixel(key: string, pixel: Pixel): void { - console.log("setPixel", key, pixel) - this.state[key] = pixel + } + + public getPixel(coord: Coordinate): Pixel | undefined { + const key = `${coord[0]}_${coord[1]}`; + return this.state[key]; + } + + public deletePixel(key: string): void { + delete this.state[key]; + } + + public setPixel(key: string, pixel: Pixel): void { + console.log("setPixel", key, pixel); + this.state[key] = pixel; + } + + public setPixelColor(coord: Coordinate, color: number): void { + const key = makeString(coord); + let pixel = this.state[key]; + + if (!pixel) { + pixel = { + action: "", + color: color, + owner: "", + text: "", + timestamp: Date.now(), + x: coord[0], + y: coord[1], + } as Pixel; + } else { + pixel = { + ...pixel, + color, + }; } - public setPixelColor(coord: Coordinate, color: number): void { - const key = makeString(coord) - let pixel = this.state[key] - - if (!pixel) { - pixel = { - action: "", - color: color, - owner: "", - text: "", - timestamp: Date.now(), - x: coord[0], - y: coord[1], - } as Pixel - } else { - pixel = { - ...pixel, - color, - } - } - - this.setPixel(key, pixel) - } + this.setPixel(key, pixel); + } - public setPixels(pixels: Pixel[]): void { - for (const p of pixels) { - this.setPixel(`${p.x}_${p.y}`, p) - } - this.eventEmitter.emit("cacheUpdated", Date.now()) - console.log("p up") + public setPixels(pixels: Pixel[]): void { + for (const p of pixels) { + this.setPixel(`${p.x}_${p.y}`, p); } + this.eventEmitter.emit("cacheUpdated", Date.now()); + console.log("p up"); + } - public updateCache() {} + public updateCache() {} - public setCacheUpdated(value: number): void { - this.cacheUpdated = value - } + public setCacheUpdated(value: number): void { + this.cacheUpdated = value; + } - public getCacheUpdated(): number { - return this.cacheUpdated - } + public getCacheUpdated(): number { + return this.cacheUpdated; + } } export function createSqlQuery(bounds: Bounds) { - const [[left, top], [right, bottom]] = bounds + const [[left, top], [right, bottom]] = bounds; - let result = `SELECT + let result = `SELECT json_array(P.color, ltrim(substr(P.text, 32), '0'), ltrim(substr(P.action, 3), '0'), (P."position.x" << 16) | P."position.y", ltrim(substr(A.name, 4), '0' )) as d FROM "pixelaw-Pixel" as P INNER JOIN "Pixelaw-App" as A ON P.app = A.system WHERE (P."position.x" >= ${left} AND P."position.y" >= ${top} AND P."position.x" <= ${right} AND P."position.y" <= ${bottom} ) - ` + `; - result += ";" - return result + result += ";"; + return result; } -export default DojoSqlPixelStore +export default DojoSqlPixelStore; diff --git a/packages/core-dojo/src/DojoSqlPixelStore.webworker.js b/packages/core-dojo/src/DojoSqlPixelStore.webworker.js index 78cb938..1eed286 100644 --- a/packages/core-dojo/src/DojoSqlPixelStore.webworker.js +++ b/packages/core-dojo/src/DojoSqlPixelStore.webworker.js @@ -1,49 +1,49 @@ -import { convertFullHexString, parseText } from "./utils/utils.ts" +import { convertFullHexString, parseText } from "./utils/utils.ts"; -let postMessageFunction +let postMessageFunction; if (typeof self !== "undefined") { - // Browser environment - postMessageFunction = globalThis.self.postMessage.bind(globalThis.self) - globalThis.self.onmessage = handleMessage + // Browser environment + postMessageFunction = globalThis.self.postMessage.bind(globalThis.self); + globalThis.self.onmessage = handleMessage; } else { - // Node.js environment - const { parentPort } = await import("node:worker_threads") - if (parentPort) { - postMessageFunction = (message) => parentPort.postMessage(message) - parentPort.on("message", handleMessage) - } + // Node.js environment + const { parentPort } = await import("node:worker_threads"); + if (parentPort) { + postMessageFunction = (message) => parentPort.postMessage(message); + parentPort.on("message", handleMessage); + } } async function handleMessage(event) { - const data = event.data ? event.data : event - const query = data.query - const toriiUrl = data.toriiUrl - - try { - const result = {} - const response = await fetch(`${toriiUrl}/sql?query=${query}`) - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`) - } - const json = await response.json() + const data = event.data ? event.data : event; + const query = data.query; + const toriiUrl = data.toriiUrl; - for (const j of json) { - const d = JSON.parse(j.d) + try { + const result = {}; + const response = await fetch(`${toriiUrl}/sql?query=${query}`); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + const json = await response.json(); - const color = d[0] - const x = d[3] >> 16 - const y = d[3] & 0xffff - const text = convertFullHexString(d[1]) - const action = parseText(d[2]) - const app = parseText(d[4]) - const pixel = { color, x, y, text, action, app } - const key = `${x}_${y}` - result[key] = pixel - } + for (const j of json) { + const d = JSON.parse(j.d); - postMessageFunction({ success: true, data: result }) - } catch (error) { - postMessageFunction({ success: false, error: error.message }) + const color = d[0]; + const x = d[3] >> 16; + const y = d[3] & 0xffff; + const text = convertFullHexString(d[1]); + const action = parseText(d[2]); + const app = parseText(d[4]); + const pixel = { color, x, y, text, action, app }; + const key = `${x}_${y}`; + result[key] = pixel; } + + postMessageFunction({ success: true, data: result }); + } catch (error) { + postMessageFunction({ success: false, error: error.message }); + } } diff --git a/packages/core-dojo/src/DojoWallet.ts b/packages/core-dojo/src/DojoWallet.ts index 9b2e3e9..f5163c5 100644 --- a/packages/core-dojo/src/DojoWallet.ts +++ b/packages/core-dojo/src/DojoWallet.ts @@ -1,35 +1,39 @@ -import { type BaseWallet, Wallet } from "@pixelaw/core" -import type { AccountInterface } from "starknet" -import { ENGINE_ID } from "./types.ts" +import { type BaseWallet, Wallet } from "@pixelaw/core"; +import type { AccountInterface } from "starknet"; +import { ENGINE_ID } from "./types.ts"; export type DojoWalletId = - | "argentX" - | "argentMobile" - | "argentWeb" - | "braavos" - | "burner" - | "predeployed" - | "controller" + | "argentX" + | "argentMobile" + | "argentWeb" + | "braavos" + | "burner" + | "predeployed" + | "controller"; export class DojoWallet extends Wallet { - private _account: AccountInterface - private _isConnected = false + private _account: AccountInterface; + private _isConnected = false; - constructor(walletId: DojoWalletId, chainId: string, account: AccountInterface) { - super(ENGINE_ID, walletId, account.address, chainId) - this._account = account - } + constructor( + walletId: DojoWalletId, + chainId: string, + account: AccountInterface, + ) { + super(ENGINE_ID, walletId, account.address, chainId); + this._account = account; + } - get account(): AccountInterface { - return this._account - } + get account(): AccountInterface { + return this._account; + } - get isConnected(): boolean { - return this._isConnected - } + get isConnected(): boolean { + return this._isConnected; + } - toJSON(): BaseWallet { - const { engine, id, address, chainId } = this - return { engine, id, address, chainId } - } + toJSON(): BaseWallet { + const { engine, id, address, chainId } = this; + return { engine, id, address, chainId }; + } } diff --git a/packages/core-dojo/src/__tests__/sim.json b/packages/core-dojo/src/__tests__/sim.json index 3263959..b571a3f 100644 --- a/packages/core-dojo/src/__tests__/sim.json +++ b/packages/core-dojo/src/__tests__/sim.json @@ -68,7 +68,10 @@ "steps": 1152 }, "messages": [], - "result": ["0x1", "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b"] + "result": [ + "0x1", + "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b" + ] }, { "call_type": "CALL", @@ -97,7 +100,10 @@ "steps": 1152 }, "messages": [], - "result": ["0x1", "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b"] + "result": [ + "0x1", + "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b" + ] }, { "call_type": "CALL", @@ -162,7 +168,18 @@ "steps": 9187 }, "messages": [], - "result": ["0x9", "0x0", "0xf09f8d87", "0xa", "0xe", "0xff7f00ff", "0x0", "0x0", "0x0", "0x0"] + "result": [ + "0x9", + "0x0", + "0xf09f8d87", + "0xa", + "0xe", + "0xff7f00ff", + "0x0", + "0x0", + "0x0", + "0x0" + ] }, { "call_type": "CALL", @@ -191,7 +208,10 @@ "steps": 1152 }, "messages": [], - "result": ["0x1", "0x5f59e1c371465a83f4a10d040627be37967c02e8fbe5c5f0afe1068e7cd0718"] + "result": [ + "0x1", + "0x5f59e1c371465a83f4a10d040627be37967c02e8fbe5c5f0afe1068e7cd0718" + ] }, { "call_type": "CALL", @@ -242,7 +262,10 @@ "steps": 1152 }, "messages": [], - "result": ["0x1", "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b"] + "result": [ + "0x1", + "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b" + ] }, { "call_type": "CALL", @@ -449,7 +472,21 @@ "steps": 29027 }, "messages": [], - "result": ["0x0", "0xa", "0xe", "0x0", "0x0", "0x1", "0x0", "0x0", "0x0", "0x0", "0x1", "0x0", "0x0"] + "result": [ + "0x0", + "0xa", + "0xe", + "0x0", + "0x0", + "0x1", + "0x0", + "0x0", + "0x0", + "0x0", + "0x1", + "0x0", + "0x0" + ] }, { "call_type": "CALL", @@ -507,7 +544,17 @@ "steps": 7975 }, "messages": [], - "result": ["0x8", "0x0", "0x0", "0x0", "0x0", "0x0", "0x0", "0x0", "0x0"] + "result": [ + "0x8", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] }, { "call_type": "CALL", @@ -687,7 +734,10 @@ "steps": 1152 }, "messages": [], - "result": ["0x1", "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b"] + "result": [ + "0x1", + "0x6e82adcb82e399385f7a2fb6a208dea94715351f0eea2978cd5fe0410d92e5b" + ] }, { "call_type": "CALL", @@ -745,7 +795,17 @@ "steps": 7975 }, "messages": [], - "result": ["0x8", "0x0", "0x0", "0x0", "0x0", "0x0", "0x0", "0x0", "0x0"] + "result": [ + "0x8", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] }, { "call_type": "CALL", diff --git a/packages/core-dojo/src/__tests__/simparser.test.ts b/packages/core-dojo/src/__tests__/simparser.test.ts index b363ba7..5a4c1c6 100644 --- a/packages/core-dojo/src/__tests__/simparser.test.ts +++ b/packages/core-dojo/src/__tests__/simparser.test.ts @@ -1,17 +1,19 @@ -import * as fs from "node:fs" -import { describe, expect, it } from "vitest" -import { parseEventsFromSimulation } from "../utils/parseEvents" +import * as fs from "node:fs"; +import { describe, expect, it } from "vitest"; +import { parseEventsFromSimulation } from "../utils/parseEvents"; -const simJson = JSON.parse(fs.readFileSync(`${process.cwd()}/src/__tests__/sim.json`, "utf-8")) +const simJson = JSON.parse( + fs.readFileSync(`${process.cwd()}/src/__tests__/sim.json`, "utf-8"), +); describe("parseEventsFromSimulation", () => { - it("should correctly parse events with valid data", () => { - const res = parseEventsFromSimulation( - "0x7e607b2fbb4cfb3fb9d1258fa2ff3aa94f17b3820e42bf1e6a43e2de3f5772e", - simJson, - ) - console.info(res) - // Test implementation for valid data - expect(true).toBe(true) // Replace with actual test - }) -}) + it("should correctly parse events with valid data", () => { + const res = parseEventsFromSimulation( + "0x7e607b2fbb4cfb3fb9d1258fa2ff3aa94f17b3820e42bf1e6a43e2de3f5772e", + simJson, + ); + console.info(res); + // Test implementation for valid data + expect(true).toBe(true); // Replace with actual test + }); +}); diff --git a/packages/core-dojo/src/generated/contracts.gen.ts b/packages/core-dojo/src/generated/contracts.gen.ts index f2b8aac..0dd899a 100644 --- a/packages/core-dojo/src/generated/contracts.gen.ts +++ b/packages/core-dojo/src/generated/contracts.gen.ts @@ -1,709 +1,831 @@ -import type { DojoCall, DojoProvider } from "@dojoengine/core" -import type { Account, AccountInterface, BigNumberish, CairoCustomEnum, CairoOption } from "starknet" -import type * as models from "./models.gen" +import type { DojoCall, DojoProvider } from "@dojoengine/core"; +import type { + Account, + AccountInterface, + BigNumberish, + CairoCustomEnum, + CairoOption, +} from "starknet"; +import type * as models from "./models.gen"; export function setupWorld(provider: DojoProvider) { - const build_actions_addArea_calldata = ( - bounds: models.Bounds, - owner: string, - color: BigNumberish, - app: string, - ): DojoCall => { - return { - contractName: "actions", - entrypoint: "add_area", - calldata: [bounds, owner, color, app], - } - } - - const actions_addArea = async ( - snAccount: Account | AccountInterface, - bounds: models.Bounds, - owner: string, - color: BigNumberish, - app: string, - ) => { - try { - // @ts-ignore - return await provider.execute( - // @ts-ignore - snAccount, - build_actions_addArea_calldata(bounds, owner, color, app), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_canUpdatePixel_calldata = ( - forPlayer: string, - forSystem: string, - pixel: models.Pixel, - pixelUpdate: models.PixelUpdate, - areaIdHint: CairoOption, - allowModify: boolean, - ): DojoCall => { - return { - contractName: "actions", - entrypoint: "can_update_pixel", - calldata: [forPlayer, forSystem, pixel, pixelUpdate, areaIdHint, allowModify], - } - } - - const actions_canUpdatePixel = async ( - snAccount: Account | AccountInterface, - forPlayer: string, - forSystem: string, - pixel: models.Pixel, - pixelUpdate: models.PixelUpdate, - areaIdHint: CairoOption, - allowModify: boolean, - ) => { - try { - return await provider.execute( - snAccount, - build_actions_canUpdatePixel_calldata( - forPlayer, - forSystem, - pixel, - pixelUpdate, - areaIdHint, - allowModify, - ), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_findAreaByPosition_calldata = (position: models.Position): DojoCall => { - return { - contractName: "actions", - entrypoint: "find_area_by_position", - calldata: [position], - } - } - - const actions_findAreaByPosition = async (snAccount: Account | AccountInterface, position: models.Position) => { - try { - return await provider.execute(snAccount, build_actions_findAreaByPosition_calldata(position), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_findAreasInsideBounds_calldata = (bounds: models.Bounds): DojoCall => { - return { - contractName: "actions", - entrypoint: "find_areas_inside_bounds", - calldata: [bounds], - } - } - - const actions_findAreasInsideBounds = async (snAccount: Account | AccountInterface, bounds: models.Bounds) => { - try { - return await provider.execute(snAccount, build_actions_findAreasInsideBounds_calldata(bounds), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_newApp_calldata = (system: string, name: BigNumberish, icon: BigNumberish): DojoCall => { - return { - contractName: "actions", - entrypoint: "new_app", - calldata: [system, name, icon], - } - } - - const actions_newApp = async ( - snAccount: Account | AccountInterface, - system: string, - name: BigNumberish, - icon: BigNumberish, - ) => { - try { - return await provider.execute(snAccount, build_actions_newApp_calldata(system, name, icon), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_notification_calldata = ( - position: models.Position, - color: BigNumberish, - from: CairoOption, - to: CairoOption, - text: BigNumberish, - ): DojoCall => { - return { - contractName: "actions", - entrypoint: "notification", - calldata: [position, color, from, to, text], - } - } - - const actions_notification = async ( - snAccount: Account | AccountInterface, - position: models.Position, - color: BigNumberish, - from: CairoOption, - to: CairoOption, - text: BigNumberish, - ) => { - try { - return await provider.execute( - snAccount, - build_actions_notification_calldata(position, color, from, to, text), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_processQueue_calldata = ( - id: BigNumberish, - timestamp: BigNumberish, - calledSystem: string, - selector: BigNumberish, - calldata: Array, - ): DojoCall => { - return { - contractName: "actions", - entrypoint: "process_queue", - calldata: [id, timestamp, calledSystem, selector, calldata], - } - } - - const actions_processQueue = async ( - snAccount: Account | AccountInterface, - id: BigNumberish, - timestamp: BigNumberish, - calledSystem: string, - selector: BigNumberish, - calldata: Array, - ) => { - try { - return await provider.execute( - snAccount, - build_actions_processQueue_calldata(id, timestamp, calledSystem, selector, calldata), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_removeArea_calldata = (areaId: BigNumberish): DojoCall => { - return { - contractName: "actions", - entrypoint: "remove_area", - calldata: [areaId], - } - } - - const actions_removeArea = async (snAccount: Account | AccountInterface, areaId: BigNumberish) => { - try { - return await provider.execute(snAccount, build_actions_removeArea_calldata(areaId), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_scheduleQueue_calldata = ( - timestamp: BigNumberish, - calledSystem: string, - selector: BigNumberish, - calldata: Array, - ): DojoCall => { - return { - contractName: "actions", - entrypoint: "schedule_queue", - calldata: [timestamp, calledSystem, selector, calldata], - } - } - - const actions_scheduleQueue = async ( - snAccount: Account | AccountInterface, - timestamp: BigNumberish, - calledSystem: string, - selector: BigNumberish, - calldata: Array, - ) => { - try { - return await provider.execute( - snAccount, - build_actions_scheduleQueue_calldata(timestamp, calledSystem, selector, calldata), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_actions_updatePixel_calldata = ( - forPlayer: string, - forSystem: string, - pixelUpdate: models.PixelUpdate, - areaId: CairoOption, - allowModify: boolean, - ): DojoCall => { - return { - contractName: "actions", - entrypoint: "update_pixel", - calldata: [forPlayer, forSystem, pixelUpdate, areaId, allowModify], - } - } - - const actions_updatePixel = async ( - snAccount: Account | AccountInterface, - forPlayer: string, - forSystem: string, - pixelUpdate: models.PixelUpdate, - areaId: CairoOption, - allowModify: boolean, - ) => { - try { - return await provider.execute( - snAccount, - build_actions_updatePixel_calldata(forPlayer, forSystem, pixelUpdate, areaId, allowModify), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_paint_actions_fade_calldata = (defaultParams: models.DefaultParameters): DojoCall => { - return { - contractName: "paint_actions", - entrypoint: "fade", - calldata: [defaultParams], - } - } - - const paint_actions_fade = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - ) => { - try { - return await provider.execute(snAccount, build_paint_actions_fade_calldata(defaultParams), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_paint_actions_interact_calldata = (defaultParams: models.DefaultParameters): DojoCall => { - return { - contractName: "paint_actions", - entrypoint: "interact", - calldata: [defaultParams], - } - } - - const paint_actions_interact = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - ) => { - try { - return await provider.execute(snAccount, build_paint_actions_interact_calldata(defaultParams), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_paint_actions_onPostUpdate_calldata = ( - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ): DojoCall => { - return { - contractName: "paint_actions", - entrypoint: "on_post_update", - calldata: [pixelUpdate, appCaller, playerCaller], - } - } - - const paint_actions_onPostUpdate = async ( - snAccount: Account | AccountInterface, - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ) => { - try { - return await provider.execute( - snAccount, - build_paint_actions_onPostUpdate_calldata(pixelUpdate, appCaller, playerCaller), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_paint_actions_onPreUpdate_calldata = ( - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ): DojoCall => { - return { - contractName: "paint_actions", - entrypoint: "on_pre_update", - calldata: [pixelUpdate, appCaller, playerCaller], - } - } - - const paint_actions_onPreUpdate = async ( - snAccount: Account | AccountInterface, - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ) => { - try { - return await provider.execute( - snAccount, - build_paint_actions_onPreUpdate_calldata(pixelUpdate, appCaller, playerCaller), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_paint_actions_pixelRow_calldata = ( - defaultParams: models.DefaultParameters, - imageData: Array, - ): DojoCall => { - return { - contractName: "paint_actions", - entrypoint: "pixel_row", - calldata: [defaultParams, imageData], - } - } - - const paint_actions_pixelRow = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - imageData: Array, - ) => { - try { - return await provider.execute( - snAccount, - build_paint_actions_pixelRow_calldata(defaultParams, imageData), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_paint_actions_putColor_calldata = (defaultParams: models.DefaultParameters): DojoCall => { - return { - contractName: "paint_actions", - entrypoint: "put_color", - calldata: [defaultParams], - } - } - - const paint_actions_putColor = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - ) => { - try { - return await provider.execute(snAccount, build_paint_actions_putColor_calldata(defaultParams), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_player_actions_configure_calldata = ( - defaultParams: models.DefaultParameters, - emoji: models.Emoji, - ): DojoCall => { - return { - contractName: "player_actions", - entrypoint: "configure", - calldata: [defaultParams, emoji], - } - } - - const player_actions_configure = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - emoji: models.Emoji, - ) => { - try { - return await provider.execute( - snAccount, - build_player_actions_configure_calldata(defaultParams, emoji), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_player_actions_interact_calldata = (defaultParams: models.DefaultParameters): DojoCall => { - return { - contractName: "player_actions", - entrypoint: "interact", - calldata: [defaultParams], - } - } - - const player_actions_interact = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - ) => { - try { - return await provider.execute(snAccount, build_player_actions_interact_calldata(defaultParams), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_player_actions_onPostUpdate_calldata = ( - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ): DojoCall => { - return { - contractName: "player_actions", - entrypoint: "on_post_update", - calldata: [pixelUpdate, appCaller, playerCaller], - } - } - - const player_actions_onPostUpdate = async ( - snAccount: Account | AccountInterface, - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ) => { - try { - return await provider.execute( - snAccount, - build_player_actions_onPostUpdate_calldata(pixelUpdate, appCaller, playerCaller), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_player_actions_onPreUpdate_calldata = ( - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ): DojoCall => { - return { - contractName: "player_actions", - entrypoint: "on_pre_update", - calldata: [pixelUpdate, appCaller, playerCaller], - } - } - - const player_actions_onPreUpdate = async ( - snAccount: Account | AccountInterface, - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ) => { - try { - return await provider.execute( - snAccount, - build_player_actions_onPreUpdate_calldata(pixelUpdate, appCaller, playerCaller), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_snake_actions_interact_calldata = ( - defaultParams: models.DefaultParameters, - direction: CairoCustomEnum, - ): DojoCall => { - return { - contractName: "snake_actions", - entrypoint: "interact", - calldata: [defaultParams, direction], - } - } - - const snake_actions_interact = async ( - snAccount: Account | AccountInterface, - defaultParams: models.DefaultParameters, - direction: CairoCustomEnum, - ) => { - try { - return await provider.execute( - snAccount, - build_snake_actions_interact_calldata(defaultParams, direction), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_snake_actions_move_calldata = (owner: string): DojoCall => { - return { - contractName: "snake_actions", - entrypoint: "move", - calldata: [owner], - } - } - - const snake_actions_move = async (snAccount: Account | AccountInterface, owner: string) => { - try { - return await provider.execute(snAccount, build_snake_actions_move_calldata(owner), "pixelaw") - } catch (error) { - console.error(error) - throw error - } - } - - const build_snake_actions_onPostUpdate_calldata = ( - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ): DojoCall => { - return { - contractName: "snake_actions", - entrypoint: "on_post_update", - calldata: [pixelUpdate, appCaller, playerCaller], - } - } - - const snake_actions_onPostUpdate = async ( - snAccount: Account | AccountInterface, - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ) => { - try { - return await provider.execute( - snAccount, - build_snake_actions_onPostUpdate_calldata(pixelUpdate, appCaller, playerCaller), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - - const build_snake_actions_onPreUpdate_calldata = ( - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ): DojoCall => { - return { - contractName: "snake_actions", - entrypoint: "on_pre_update", - calldata: [pixelUpdate, appCaller, playerCaller], - } - } - - const snake_actions_onPreUpdate = async ( - snAccount: Account | AccountInterface, - pixelUpdate: models.PixelUpdate, - appCaller: models.App, - playerCaller: string, - ) => { - try { - return await provider.execute( - snAccount, - build_snake_actions_onPreUpdate_calldata(pixelUpdate, appCaller, playerCaller), - "pixelaw", - ) - } catch (error) { - console.error(error) - throw error - } - } - + const build_actions_addArea_calldata = ( + bounds: models.Bounds, + owner: string, + color: BigNumberish, + app: string, + ): DojoCall => { return { - actions: { - addArea: actions_addArea, - buildAddAreaCalldata: build_actions_addArea_calldata, - canUpdatePixel: actions_canUpdatePixel, - buildCanUpdatePixelCalldata: build_actions_canUpdatePixel_calldata, - findAreaByPosition: actions_findAreaByPosition, - buildFindAreaByPositionCalldata: build_actions_findAreaByPosition_calldata, - findAreasInsideBounds: actions_findAreasInsideBounds, - buildFindAreasInsideBoundsCalldata: build_actions_findAreasInsideBounds_calldata, - newApp: actions_newApp, - buildNewAppCalldata: build_actions_newApp_calldata, - notification: actions_notification, - buildNotificationCalldata: build_actions_notification_calldata, - processQueue: actions_processQueue, - buildProcessQueueCalldata: build_actions_processQueue_calldata, - removeArea: actions_removeArea, - buildRemoveAreaCalldata: build_actions_removeArea_calldata, - scheduleQueue: actions_scheduleQueue, - buildScheduleQueueCalldata: build_actions_scheduleQueue_calldata, - updatePixel: actions_updatePixel, - buildUpdatePixelCalldata: build_actions_updatePixel_calldata, - }, - paint_actions: { - fade: paint_actions_fade, - buildFadeCalldata: build_paint_actions_fade_calldata, - interact: paint_actions_interact, - buildInteractCalldata: build_paint_actions_interact_calldata, - onPostUpdate: paint_actions_onPostUpdate, - buildOnPostUpdateCalldata: build_paint_actions_onPostUpdate_calldata, - onPreUpdate: paint_actions_onPreUpdate, - buildOnPreUpdateCalldata: build_paint_actions_onPreUpdate_calldata, - pixelRow: paint_actions_pixelRow, - buildPixelRowCalldata: build_paint_actions_pixelRow_calldata, - putColor: paint_actions_putColor, - buildPutColorCalldata: build_paint_actions_putColor_calldata, - }, - player_actions: { - configure: player_actions_configure, - buildConfigureCalldata: build_player_actions_configure_calldata, - interact: player_actions_interact, - buildInteractCalldata: build_player_actions_interact_calldata, - onPostUpdate: player_actions_onPostUpdate, - buildOnPostUpdateCalldata: build_player_actions_onPostUpdate_calldata, - onPreUpdate: player_actions_onPreUpdate, - buildOnPreUpdateCalldata: build_player_actions_onPreUpdate_calldata, - }, - snake_actions: { - interact: snake_actions_interact, - buildInteractCalldata: build_snake_actions_interact_calldata, - move: snake_actions_move, - buildMoveCalldata: build_snake_actions_move_calldata, - onPostUpdate: snake_actions_onPostUpdate, - buildOnPostUpdateCalldata: build_snake_actions_onPostUpdate_calldata, - onPreUpdate: snake_actions_onPreUpdate, - buildOnPreUpdateCalldata: build_snake_actions_onPreUpdate_calldata, - }, - } + contractName: "actions", + entrypoint: "add_area", + calldata: [bounds, owner, color, app], + }; + }; + + const actions_addArea = async ( + snAccount: Account | AccountInterface, + bounds: models.Bounds, + owner: string, + color: BigNumberish, + app: string, + ) => { + try { + // @ts-ignore + return await provider.execute( + // @ts-ignore + snAccount, + build_actions_addArea_calldata(bounds, owner, color, app), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_canUpdatePixel_calldata = ( + forPlayer: string, + forSystem: string, + pixel: models.Pixel, + pixelUpdate: models.PixelUpdate, + areaIdHint: CairoOption, + allowModify: boolean, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "can_update_pixel", + calldata: [ + forPlayer, + forSystem, + pixel, + pixelUpdate, + areaIdHint, + allowModify, + ], + }; + }; + + const actions_canUpdatePixel = async ( + snAccount: Account | AccountInterface, + forPlayer: string, + forSystem: string, + pixel: models.Pixel, + pixelUpdate: models.PixelUpdate, + areaIdHint: CairoOption, + allowModify: boolean, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_canUpdatePixel_calldata( + forPlayer, + forSystem, + pixel, + pixelUpdate, + areaIdHint, + allowModify, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_findAreaByPosition_calldata = ( + position: models.Position, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "find_area_by_position", + calldata: [position], + }; + }; + + const actions_findAreaByPosition = async ( + snAccount: Account | AccountInterface, + position: models.Position, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_findAreaByPosition_calldata(position), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_findAreasInsideBounds_calldata = ( + bounds: models.Bounds, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "find_areas_inside_bounds", + calldata: [bounds], + }; + }; + + const actions_findAreasInsideBounds = async ( + snAccount: Account | AccountInterface, + bounds: models.Bounds, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_findAreasInsideBounds_calldata(bounds), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_newApp_calldata = ( + system: string, + name: BigNumberish, + icon: BigNumberish, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "new_app", + calldata: [system, name, icon], + }; + }; + + const actions_newApp = async ( + snAccount: Account | AccountInterface, + system: string, + name: BigNumberish, + icon: BigNumberish, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_newApp_calldata(system, name, icon), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_notification_calldata = ( + position: models.Position, + color: BigNumberish, + from: CairoOption, + to: CairoOption, + text: BigNumberish, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "notification", + calldata: [position, color, from, to, text], + }; + }; + + const actions_notification = async ( + snAccount: Account | AccountInterface, + position: models.Position, + color: BigNumberish, + from: CairoOption, + to: CairoOption, + text: BigNumberish, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_notification_calldata(position, color, from, to, text), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_processQueue_calldata = ( + id: BigNumberish, + timestamp: BigNumberish, + calledSystem: string, + selector: BigNumberish, + calldata: Array, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "process_queue", + calldata: [id, timestamp, calledSystem, selector, calldata], + }; + }; + + const actions_processQueue = async ( + snAccount: Account | AccountInterface, + id: BigNumberish, + timestamp: BigNumberish, + calledSystem: string, + selector: BigNumberish, + calldata: Array, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_processQueue_calldata( + id, + timestamp, + calledSystem, + selector, + calldata, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_removeArea_calldata = ( + areaId: BigNumberish, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "remove_area", + calldata: [areaId], + }; + }; + + const actions_removeArea = async ( + snAccount: Account | AccountInterface, + areaId: BigNumberish, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_removeArea_calldata(areaId), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_scheduleQueue_calldata = ( + timestamp: BigNumberish, + calledSystem: string, + selector: BigNumberish, + calldata: Array, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "schedule_queue", + calldata: [timestamp, calledSystem, selector, calldata], + }; + }; + + const actions_scheduleQueue = async ( + snAccount: Account | AccountInterface, + timestamp: BigNumberish, + calledSystem: string, + selector: BigNumberish, + calldata: Array, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_scheduleQueue_calldata( + timestamp, + calledSystem, + selector, + calldata, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_updatePixel_calldata = ( + forPlayer: string, + forSystem: string, + pixelUpdate: models.PixelUpdate, + areaId: CairoOption, + allowModify: boolean, + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "update_pixel", + calldata: [forPlayer, forSystem, pixelUpdate, areaId, allowModify], + }; + }; + + const actions_updatePixel = async ( + snAccount: Account | AccountInterface, + forPlayer: string, + forSystem: string, + pixelUpdate: models.PixelUpdate, + areaId: CairoOption, + allowModify: boolean, + ) => { + try { + return await provider.execute( + snAccount, + build_actions_updatePixel_calldata( + forPlayer, + forSystem, + pixelUpdate, + areaId, + allowModify, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_paint_actions_fade_calldata = ( + defaultParams: models.DefaultParameters, + ): DojoCall => { + return { + contractName: "paint_actions", + entrypoint: "fade", + calldata: [defaultParams], + }; + }; + + const paint_actions_fade = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + ) => { + try { + return await provider.execute( + snAccount, + build_paint_actions_fade_calldata(defaultParams), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_paint_actions_interact_calldata = ( + defaultParams: models.DefaultParameters, + ): DojoCall => { + return { + contractName: "paint_actions", + entrypoint: "interact", + calldata: [defaultParams], + }; + }; + + const paint_actions_interact = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + ) => { + try { + return await provider.execute( + snAccount, + build_paint_actions_interact_calldata(defaultParams), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_paint_actions_onPostUpdate_calldata = ( + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ): DojoCall => { + return { + contractName: "paint_actions", + entrypoint: "on_post_update", + calldata: [pixelUpdate, appCaller, playerCaller], + }; + }; + + const paint_actions_onPostUpdate = async ( + snAccount: Account | AccountInterface, + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ) => { + try { + return await provider.execute( + snAccount, + build_paint_actions_onPostUpdate_calldata( + pixelUpdate, + appCaller, + playerCaller, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_paint_actions_onPreUpdate_calldata = ( + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ): DojoCall => { + return { + contractName: "paint_actions", + entrypoint: "on_pre_update", + calldata: [pixelUpdate, appCaller, playerCaller], + }; + }; + + const paint_actions_onPreUpdate = async ( + snAccount: Account | AccountInterface, + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ) => { + try { + return await provider.execute( + snAccount, + build_paint_actions_onPreUpdate_calldata( + pixelUpdate, + appCaller, + playerCaller, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_paint_actions_pixelRow_calldata = ( + defaultParams: models.DefaultParameters, + imageData: Array, + ): DojoCall => { + return { + contractName: "paint_actions", + entrypoint: "pixel_row", + calldata: [defaultParams, imageData], + }; + }; + + const paint_actions_pixelRow = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + imageData: Array, + ) => { + try { + return await provider.execute( + snAccount, + build_paint_actions_pixelRow_calldata(defaultParams, imageData), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_paint_actions_putColor_calldata = ( + defaultParams: models.DefaultParameters, + ): DojoCall => { + return { + contractName: "paint_actions", + entrypoint: "put_color", + calldata: [defaultParams], + }; + }; + + const paint_actions_putColor = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + ) => { + try { + return await provider.execute( + snAccount, + build_paint_actions_putColor_calldata(defaultParams), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_player_actions_configure_calldata = ( + defaultParams: models.DefaultParameters, + emoji: models.Emoji, + ): DojoCall => { + return { + contractName: "player_actions", + entrypoint: "configure", + calldata: [defaultParams, emoji], + }; + }; + + const player_actions_configure = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + emoji: models.Emoji, + ) => { + try { + return await provider.execute( + snAccount, + build_player_actions_configure_calldata(defaultParams, emoji), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_player_actions_interact_calldata = ( + defaultParams: models.DefaultParameters, + ): DojoCall => { + return { + contractName: "player_actions", + entrypoint: "interact", + calldata: [defaultParams], + }; + }; + + const player_actions_interact = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + ) => { + try { + return await provider.execute( + snAccount, + build_player_actions_interact_calldata(defaultParams), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_player_actions_onPostUpdate_calldata = ( + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ): DojoCall => { + return { + contractName: "player_actions", + entrypoint: "on_post_update", + calldata: [pixelUpdate, appCaller, playerCaller], + }; + }; + + const player_actions_onPostUpdate = async ( + snAccount: Account | AccountInterface, + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ) => { + try { + return await provider.execute( + snAccount, + build_player_actions_onPostUpdate_calldata( + pixelUpdate, + appCaller, + playerCaller, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_player_actions_onPreUpdate_calldata = ( + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ): DojoCall => { + return { + contractName: "player_actions", + entrypoint: "on_pre_update", + calldata: [pixelUpdate, appCaller, playerCaller], + }; + }; + + const player_actions_onPreUpdate = async ( + snAccount: Account | AccountInterface, + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ) => { + try { + return await provider.execute( + snAccount, + build_player_actions_onPreUpdate_calldata( + pixelUpdate, + appCaller, + playerCaller, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_snake_actions_interact_calldata = ( + defaultParams: models.DefaultParameters, + direction: CairoCustomEnum, + ): DojoCall => { + return { + contractName: "snake_actions", + entrypoint: "interact", + calldata: [defaultParams, direction], + }; + }; + + const snake_actions_interact = async ( + snAccount: Account | AccountInterface, + defaultParams: models.DefaultParameters, + direction: CairoCustomEnum, + ) => { + try { + return await provider.execute( + snAccount, + build_snake_actions_interact_calldata(defaultParams, direction), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_snake_actions_move_calldata = (owner: string): DojoCall => { + return { + contractName: "snake_actions", + entrypoint: "move", + calldata: [owner], + }; + }; + + const snake_actions_move = async ( + snAccount: Account | AccountInterface, + owner: string, + ) => { + try { + return await provider.execute( + snAccount, + build_snake_actions_move_calldata(owner), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_snake_actions_onPostUpdate_calldata = ( + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ): DojoCall => { + return { + contractName: "snake_actions", + entrypoint: "on_post_update", + calldata: [pixelUpdate, appCaller, playerCaller], + }; + }; + + const snake_actions_onPostUpdate = async ( + snAccount: Account | AccountInterface, + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ) => { + try { + return await provider.execute( + snAccount, + build_snake_actions_onPostUpdate_calldata( + pixelUpdate, + appCaller, + playerCaller, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_snake_actions_onPreUpdate_calldata = ( + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ): DojoCall => { + return { + contractName: "snake_actions", + entrypoint: "on_pre_update", + calldata: [pixelUpdate, appCaller, playerCaller], + }; + }; + + const snake_actions_onPreUpdate = async ( + snAccount: Account | AccountInterface, + pixelUpdate: models.PixelUpdate, + appCaller: models.App, + playerCaller: string, + ) => { + try { + return await provider.execute( + snAccount, + build_snake_actions_onPreUpdate_calldata( + pixelUpdate, + appCaller, + playerCaller, + ), + "pixelaw", + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + return { + actions: { + addArea: actions_addArea, + buildAddAreaCalldata: build_actions_addArea_calldata, + canUpdatePixel: actions_canUpdatePixel, + buildCanUpdatePixelCalldata: build_actions_canUpdatePixel_calldata, + findAreaByPosition: actions_findAreaByPosition, + buildFindAreaByPositionCalldata: + build_actions_findAreaByPosition_calldata, + findAreasInsideBounds: actions_findAreasInsideBounds, + buildFindAreasInsideBoundsCalldata: + build_actions_findAreasInsideBounds_calldata, + newApp: actions_newApp, + buildNewAppCalldata: build_actions_newApp_calldata, + notification: actions_notification, + buildNotificationCalldata: build_actions_notification_calldata, + processQueue: actions_processQueue, + buildProcessQueueCalldata: build_actions_processQueue_calldata, + removeArea: actions_removeArea, + buildRemoveAreaCalldata: build_actions_removeArea_calldata, + scheduleQueue: actions_scheduleQueue, + buildScheduleQueueCalldata: build_actions_scheduleQueue_calldata, + updatePixel: actions_updatePixel, + buildUpdatePixelCalldata: build_actions_updatePixel_calldata, + }, + paint_actions: { + fade: paint_actions_fade, + buildFadeCalldata: build_paint_actions_fade_calldata, + interact: paint_actions_interact, + buildInteractCalldata: build_paint_actions_interact_calldata, + onPostUpdate: paint_actions_onPostUpdate, + buildOnPostUpdateCalldata: build_paint_actions_onPostUpdate_calldata, + onPreUpdate: paint_actions_onPreUpdate, + buildOnPreUpdateCalldata: build_paint_actions_onPreUpdate_calldata, + pixelRow: paint_actions_pixelRow, + buildPixelRowCalldata: build_paint_actions_pixelRow_calldata, + putColor: paint_actions_putColor, + buildPutColorCalldata: build_paint_actions_putColor_calldata, + }, + player_actions: { + configure: player_actions_configure, + buildConfigureCalldata: build_player_actions_configure_calldata, + interact: player_actions_interact, + buildInteractCalldata: build_player_actions_interact_calldata, + onPostUpdate: player_actions_onPostUpdate, + buildOnPostUpdateCalldata: build_player_actions_onPostUpdate_calldata, + onPreUpdate: player_actions_onPreUpdate, + buildOnPreUpdateCalldata: build_player_actions_onPreUpdate_calldata, + }, + snake_actions: { + interact: snake_actions_interact, + buildInteractCalldata: build_snake_actions_interact_calldata, + move: snake_actions_move, + buildMoveCalldata: build_snake_actions_move_calldata, + onPostUpdate: snake_actions_onPostUpdate, + buildOnPostUpdateCalldata: build_snake_actions_onPostUpdate_calldata, + onPreUpdate: snake_actions_onPreUpdate, + buildOnPreUpdateCalldata: build_snake_actions_onPreUpdate_calldata, + }, + }; } diff --git a/packages/core-dojo/src/generated/models.gen.ts b/packages/core-dojo/src/generated/models.gen.ts index 3f58a9e..3afb87c 100644 --- a/packages/core-dojo/src/generated/models.gen.ts +++ b/packages/core-dojo/src/generated/models.gen.ts @@ -1,626 +1,631 @@ -import type { SchemaType as ISchemaType } from "@dojoengine/sdk" +import type { SchemaType as ISchemaType } from "@dojoengine/sdk"; -import { CairoCustomEnum, CairoOption, CairoOptionVariant, type BigNumberish } from "starknet" +import { + CairoCustomEnum, + CairoOption, + CairoOptionVariant, + type BigNumberish, +} from "starknet"; // Type definition for `pixelaw::apps::player::Player` struct export interface Player { - owner: string - name: BigNumberish - emoji: BigNumberish - position: Position - color: BigNumberish - pixel_original_color: BigNumberish - pixel_original_app: string - pixel_original_text: BigNumberish - pixel_original_action: BigNumberish + owner: string; + name: BigNumberish; + emoji: BigNumberish; + position: Position; + color: BigNumberish; + pixel_original_color: BigNumberish; + pixel_original_app: string; + pixel_original_text: BigNumberish; + pixel_original_action: BigNumberish; } // Type definition for `pixelaw::apps::player::PlayerValue` struct export interface PlayerValue { - name: BigNumberish - emoji: BigNumberish - position: Position - color: BigNumberish - pixel_original_color: BigNumberish - pixel_original_app: string - pixel_original_text: BigNumberish - pixel_original_action: BigNumberish + name: BigNumberish; + emoji: BigNumberish; + position: Position; + color: BigNumberish; + pixel_original_color: BigNumberish; + pixel_original_app: string; + pixel_original_text: BigNumberish; + pixel_original_action: BigNumberish; } // Type definition for `pixelaw::apps::player::PositionPlayer` struct export interface PositionPlayer { - position: Position - player: string + position: Position; + player: string; } // Type definition for `pixelaw::apps::player::PositionPlayerValue` struct export interface PositionPlayerValue { - player: string + player: string; } // Type definition for `pixelaw::apps::snake::Snake` struct export interface Snake { - owner: string - length: BigNumberish - first_segment_id: BigNumberish - last_segment_id: BigNumberish - direction: DirectionEnum - color: BigNumberish - text: BigNumberish - is_dying: boolean + owner: string; + length: BigNumberish; + first_segment_id: BigNumberish; + last_segment_id: BigNumberish; + direction: DirectionEnum; + color: BigNumberish; + text: BigNumberish; + is_dying: boolean; } // Type definition for `pixelaw::apps::snake::SnakeSegment` struct export interface SnakeSegment { - id: BigNumberish - previous_id: BigNumberish - next_id: BigNumberish - position: Position - pixel_original_color: BigNumberish - pixel_original_text: BigNumberish - pixel_original_app: string + id: BigNumberish; + previous_id: BigNumberish; + next_id: BigNumberish; + position: Position; + pixel_original_color: BigNumberish; + pixel_original_text: BigNumberish; + pixel_original_app: string; } // Type definition for `pixelaw::apps::snake::SnakeSegmentValue` struct export interface SnakeSegmentValue { - previous_id: BigNumberish - next_id: BigNumberish - position: Position - pixel_original_color: BigNumberish - pixel_original_text: BigNumberish - pixel_original_app: string + previous_id: BigNumberish; + next_id: BigNumberish; + position: Position; + pixel_original_color: BigNumberish; + pixel_original_text: BigNumberish; + pixel_original_app: string; } // Type definition for `pixelaw::apps::snake::SnakeValue` struct export interface SnakeValue { - length: BigNumberish - first_segment_id: BigNumberish - last_segment_id: BigNumberish - direction: DirectionEnum - color: BigNumberish - text: BigNumberish - is_dying: boolean + length: BigNumberish; + first_segment_id: BigNumberish; + last_segment_id: BigNumberish; + direction: DirectionEnum; + color: BigNumberish; + text: BigNumberish; + is_dying: boolean; } // Type definition for `pixelaw::core::models::area::Area` struct export interface Area { - id: BigNumberish - app: string - owner: string - color: BigNumberish + id: BigNumberish; + app: string; + owner: string; + color: BigNumberish; } // Type definition for `pixelaw::core::models::area::AreaValue` struct export interface AreaValue { - app: string - owner: string - color: BigNumberish + app: string; + owner: string; + color: BigNumberish; } // Type definition for `pixelaw::core::models::area::RTree` struct export interface RTree { - id: BigNumberish - children: BigNumberish + id: BigNumberish; + children: BigNumberish; } // Type definition for `pixelaw::core::models::area::RTreeValue` struct export interface RTreeValue { - children: BigNumberish + children: BigNumberish; } // Type definition for `pixelaw::core::models::dummy::Dummy` struct export interface Dummy { - id: BigNumberish - defaultParams: DefaultParameters - bounds: Bounds - pixelUpdate: PixelUpdate - emoji: Emoji + id: BigNumberish; + defaultParams: DefaultParameters; + bounds: Bounds; + pixelUpdate: PixelUpdate; + emoji: Emoji; } // Type definition for `pixelaw::core::models::dummy::DummyValue` struct export interface DummyValue { - defaultParams: DefaultParameters - bounds: Bounds - pixelUpdate: PixelUpdate - emoji: Emoji + defaultParams: DefaultParameters; + bounds: Bounds; + pixelUpdate: PixelUpdate; + emoji: Emoji; } // Type definition for `pixelaw::core::models::pixel::Pixel` struct export interface Pixel { - position: Position - app: string - color: BigNumberish - created_at: BigNumberish - updated_at: BigNumberish - timestamp: BigNumberish - owner: string - text: BigNumberish - action: BigNumberish + position: Position; + app: string; + color: BigNumberish; + created_at: BigNumberish; + updated_at: BigNumberish; + timestamp: BigNumberish; + owner: string; + text: BigNumberish; + action: BigNumberish; } // Type definition for `pixelaw::core::models::pixel::PixelUpdate` struct export interface PixelUpdate { - position: Position - color: CairoOption - owner: CairoOption - app: CairoOption - text: CairoOption - timestamp: CairoOption - action: CairoOption + position: Position; + color: CairoOption; + owner: CairoOption; + app: CairoOption; + text: CairoOption; + timestamp: CairoOption; + action: CairoOption; } // Type definition for `pixelaw::core::models::pixel::PixelValue` struct export interface PixelValue { - app: string - color: BigNumberish - created_at: BigNumberish - updated_at: BigNumberish - timestamp: BigNumberish - owner: string - text: BigNumberish - action: BigNumberish + app: string; + color: BigNumberish; + created_at: BigNumberish; + updated_at: BigNumberish; + timestamp: BigNumberish; + owner: string; + text: BigNumberish; + action: BigNumberish; } // Type definition for `pixelaw::core::models::queue::QueueItem` struct export interface QueueItem { - id: BigNumberish - valid: boolean + id: BigNumberish; + valid: boolean; } // Type definition for `pixelaw::core::models::queue::QueueItemValue` struct export interface QueueItemValue { - valid: boolean + valid: boolean; } // Type definition for `pixelaw::core::models::registry::App` struct export interface App { - system: string - name: BigNumberish - icon: BigNumberish - action: BigNumberish + system: string; + name: BigNumberish; + icon: BigNumberish; + action: BigNumberish; } // Type definition for `pixelaw::core::models::registry::AppName` struct export interface AppName { - name: BigNumberish - system: string + name: BigNumberish; + system: string; } // Type definition for `pixelaw::core::models::registry::AppNameValue` struct export interface AppNameValue { - system: string + system: string; } // Type definition for `pixelaw::core::models::registry::AppUser` struct export interface AppUser { - system: string - player: string - action: BigNumberish + system: string; + player: string; + action: BigNumberish; } // Type definition for `pixelaw::core::models::registry::AppUserValue` struct export interface AppUserValue { - action: BigNumberish + action: BigNumberish; } // Type definition for `pixelaw::core::models::registry::AppValue` struct export interface AppValue { - name: BigNumberish - icon: BigNumberish - action: BigNumberish + name: BigNumberish; + icon: BigNumberish; + action: BigNumberish; } // Type definition for `pixelaw::core::models::registry::CoreActionsAddress` struct export interface CoreActionsAddress { - key: BigNumberish - value: string + key: BigNumberish; + value: string; } // Type definition for `pixelaw::core::models::registry::CoreActionsAddressValue` struct export interface CoreActionsAddressValue { - value: string + value: string; } // Type definition for `pixelaw::core::utils::Bounds` struct export interface Bounds { - x_min: BigNumberish - y_min: BigNumberish - x_max: BigNumberish - y_max: BigNumberish + x_min: BigNumberish; + y_min: BigNumberish; + x_max: BigNumberish; + y_max: BigNumberish; } // Type definition for `pixelaw::core::utils::DefaultParameters` struct export interface DefaultParameters { - player_override: CairoOption - system_override: CairoOption - area_hint: CairoOption - position: Position - color: BigNumberish + player_override: CairoOption; + system_override: CairoOption; + area_hint: CairoOption; + position: Position; + color: BigNumberish; } // Type definition for `pixelaw::core::utils::Emoji` struct export interface Emoji { - value: BigNumberish + value: BigNumberish; } // Type definition for `pixelaw::core::utils::Position` struct export interface Position { - x: BigNumberish - y: BigNumberish + x: BigNumberish; + y: BigNumberish; } // Type definition for `pixelaw::core::events::Notification` struct export interface Notification { - position: Position - app: string - color: BigNumberish - from: CairoOption - to: CairoOption - text: BigNumberish + position: Position; + app: string; + color: BigNumberish; + from: CairoOption; + to: CairoOption; + text: BigNumberish; } // Type definition for `pixelaw::core::events::NotificationValue` struct export interface NotificationValue { - app: string - color: BigNumberish - from: CairoOption - to: CairoOption - text: BigNumberish + app: string; + color: BigNumberish; + from: CairoOption; + to: CairoOption; + text: BigNumberish; } // Type definition for `pixelaw::core::events::QueueScheduled` struct export interface QueueScheduled { - id: BigNumberish - timestamp: BigNumberish - called_system: string - selector: BigNumberish - calldata: Array + id: BigNumberish; + timestamp: BigNumberish; + called_system: string; + selector: BigNumberish; + calldata: Array; } // Type definition for `pixelaw::core::events::QueueScheduledValue` struct export interface QueueScheduledValue { - timestamp: BigNumberish - called_system: string - selector: BigNumberish - calldata: Array + timestamp: BigNumberish; + called_system: string; + selector: BigNumberish; + calldata: Array; } // Type definition for `pixelaw::core::utils::Direction` enum -export const direction = ["None", "Left", "Right", "Up", "Down"] as const -export type Direction = { [key in (typeof direction)[number]]: string } -export type DirectionEnum = CairoCustomEnum +export const direction = ["None", "Left", "Right", "Up", "Down"] as const; +export type Direction = { [key in (typeof direction)[number]]: string }; +export type DirectionEnum = CairoCustomEnum; export interface SchemaType extends ISchemaType { - pixelaw: { - Player: Player - PlayerValue: PlayerValue - PositionPlayer: PositionPlayer - PositionPlayerValue: PositionPlayerValue - Snake: Snake - SnakeSegment: SnakeSegment - SnakeSegmentValue: SnakeSegmentValue - SnakeValue: SnakeValue - Area: Area - AreaValue: AreaValue - RTree: RTree - RTreeValue: RTreeValue - Dummy: Dummy - DummyValue: DummyValue - Pixel: Pixel - PixelUpdate: PixelUpdate - PixelValue: PixelValue - QueueItem: QueueItem - QueueItemValue: QueueItemValue - App: App - AppName: AppName - AppNameValue: AppNameValue - AppUser: AppUser - AppUserValue: AppUserValue - AppValue: AppValue - CoreActionsAddress: CoreActionsAddress - CoreActionsAddressValue: CoreActionsAddressValue - Bounds: Bounds - DefaultParameters: DefaultParameters - Emoji: Emoji - Position: Position - Notification: Notification - NotificationValue: NotificationValue - QueueScheduled: QueueScheduled - QueueScheduledValue: QueueScheduledValue - } + pixelaw: { + Player: Player; + PlayerValue: PlayerValue; + PositionPlayer: PositionPlayer; + PositionPlayerValue: PositionPlayerValue; + Snake: Snake; + SnakeSegment: SnakeSegment; + SnakeSegmentValue: SnakeSegmentValue; + SnakeValue: SnakeValue; + Area: Area; + AreaValue: AreaValue; + RTree: RTree; + RTreeValue: RTreeValue; + Dummy: Dummy; + DummyValue: DummyValue; + Pixel: Pixel; + PixelUpdate: PixelUpdate; + PixelValue: PixelValue; + QueueItem: QueueItem; + QueueItemValue: QueueItemValue; + App: App; + AppName: AppName; + AppNameValue: AppNameValue; + AppUser: AppUser; + AppUserValue: AppUserValue; + AppValue: AppValue; + CoreActionsAddress: CoreActionsAddress; + CoreActionsAddressValue: CoreActionsAddressValue; + Bounds: Bounds; + DefaultParameters: DefaultParameters; + Emoji: Emoji; + Position: Position; + Notification: Notification; + NotificationValue: NotificationValue; + QueueScheduled: QueueScheduled; + QueueScheduledValue: QueueScheduledValue; + }; } export const schema: SchemaType = { - pixelaw: { - Player: { - owner: "", - name: 0, - emoji: 0, - position: { x: 0, y: 0 }, - color: 0, - pixel_original_color: 0, - pixel_original_app: "", - pixel_original_text: 0, - pixel_original_action: 0, - }, - PlayerValue: { - name: 0, - emoji: 0, - position: { x: 0, y: 0 }, - color: 0, - pixel_original_color: 0, - pixel_original_app: "", - pixel_original_text: 0, - pixel_original_action: 0, - }, - PositionPlayer: { - position: { x: 0, y: 0 }, - player: "", - }, - PositionPlayerValue: { - player: "", - }, - Snake: { - owner: "", - length: 0, - first_segment_id: 0, - last_segment_id: 0, - direction: new CairoCustomEnum({ - None: "", - Left: undefined, - Right: undefined, - Up: undefined, - Down: undefined, - }), - color: 0, - text: 0, - is_dying: false, - }, - SnakeSegment: { - id: 0, - previous_id: 0, - next_id: 0, - position: { x: 0, y: 0 }, - pixel_original_color: 0, - pixel_original_text: 0, - pixel_original_app: "", - }, - SnakeSegmentValue: { - previous_id: 0, - next_id: 0, - position: { x: 0, y: 0 }, - pixel_original_color: 0, - pixel_original_text: 0, - pixel_original_app: "", - }, - SnakeValue: { - length: 0, - first_segment_id: 0, - last_segment_id: 0, - direction: new CairoCustomEnum({ - None: "", - Left: undefined, - Right: undefined, - Up: undefined, - Down: undefined, - }), - color: 0, - text: 0, - is_dying: false, - }, - Area: { - id: 0, - app: "", - owner: "", - color: 0, - }, - AreaValue: { - app: "", - owner: "", - color: 0, - }, - RTree: { - id: 0, - children: 0, - }, - RTreeValue: { - children: 0, - }, - Dummy: { - id: 0, - defaultParams: { - player_override: new CairoOption(CairoOptionVariant.None), - system_override: new CairoOption(CairoOptionVariant.None), - area_hint: new CairoOption(CairoOptionVariant.None), - position: { x: 0, y: 0 }, - color: 0, - }, - bounds: { x_min: 0, y_min: 0, x_max: 0, y_max: 0 }, - pixelUpdate: { - position: { x: 0, y: 0 }, - color: new CairoOption(CairoOptionVariant.None), - owner: new CairoOption(CairoOptionVariant.None), - app: new CairoOption(CairoOptionVariant.None), - text: new CairoOption(CairoOptionVariant.None), - timestamp: new CairoOption(CairoOptionVariant.None), - action: new CairoOption(CairoOptionVariant.None), - }, - emoji: { value: 0 }, - }, - DummyValue: { - defaultParams: { - player_override: new CairoOption(CairoOptionVariant.None), - system_override: new CairoOption(CairoOptionVariant.None), - area_hint: new CairoOption(CairoOptionVariant.None), - position: { x: 0, y: 0 }, - color: 0, - }, - bounds: { x_min: 0, y_min: 0, x_max: 0, y_max: 0 }, - pixelUpdate: { - position: { x: 0, y: 0 }, - color: new CairoOption(CairoOptionVariant.None), - owner: new CairoOption(CairoOptionVariant.None), - app: new CairoOption(CairoOptionVariant.None), - text: new CairoOption(CairoOptionVariant.None), - timestamp: new CairoOption(CairoOptionVariant.None), - action: new CairoOption(CairoOptionVariant.None), - }, - emoji: { value: 0 }, - }, - Pixel: { - position: { x: 0, y: 0 }, - app: "", - color: 0, - created_at: 0, - updated_at: 0, - timestamp: 0, - owner: "", - text: 0, - action: 0, - }, - PixelUpdate: { - position: { x: 0, y: 0 }, - color: new CairoOption(CairoOptionVariant.None), - owner: new CairoOption(CairoOptionVariant.None), - app: new CairoOption(CairoOptionVariant.None), - text: new CairoOption(CairoOptionVariant.None), - timestamp: new CairoOption(CairoOptionVariant.None), - action: new CairoOption(CairoOptionVariant.None), - }, - PixelValue: { - app: "", - color: 0, - created_at: 0, - updated_at: 0, - timestamp: 0, - owner: "", - text: 0, - action: 0, - }, - QueueItem: { - id: 0, - valid: false, - }, - QueueItemValue: { - valid: false, - }, - App: { - system: "", - name: 0, - icon: 0, - action: 0, - }, - AppName: { - name: 0, - system: "", - }, - AppNameValue: { - system: "", - }, - AppUser: { - system: "", - player: "", - action: 0, - }, - AppUserValue: { - action: 0, - }, - AppValue: { - name: 0, - icon: 0, - action: 0, - }, - CoreActionsAddress: { - key: 0, - value: "", - }, - CoreActionsAddressValue: { - value: "", - }, - Bounds: { - x_min: 0, - y_min: 0, - x_max: 0, - y_max: 0, - }, - DefaultParameters: { - player_override: new CairoOption(CairoOptionVariant.None), - system_override: new CairoOption(CairoOptionVariant.None), - area_hint: new CairoOption(CairoOptionVariant.None), - position: { x: 0, y: 0 }, - color: 0, - }, - Emoji: { - value: 0, - }, - Position: { - x: 0, - y: 0, - }, - Notification: { - position: { x: 0, y: 0 }, - app: "", - color: 0, - from: new CairoOption(CairoOptionVariant.None), - to: new CairoOption(CairoOptionVariant.None), - text: 0, - }, - NotificationValue: { - app: "", - color: 0, - from: new CairoOption(CairoOptionVariant.None), - to: new CairoOption(CairoOptionVariant.None), - text: 0, - }, - QueueScheduled: { - id: 0, - timestamp: 0, - called_system: "", - selector: 0, - calldata: [0], - }, - QueueScheduledValue: { - timestamp: 0, - called_system: "", - selector: 0, - calldata: [0], - }, + pixelaw: { + Player: { + owner: "", + name: 0, + emoji: 0, + position: { x: 0, y: 0 }, + color: 0, + pixel_original_color: 0, + pixel_original_app: "", + pixel_original_text: 0, + pixel_original_action: 0, }, -} + PlayerValue: { + name: 0, + emoji: 0, + position: { x: 0, y: 0 }, + color: 0, + pixel_original_color: 0, + pixel_original_app: "", + pixel_original_text: 0, + pixel_original_action: 0, + }, + PositionPlayer: { + position: { x: 0, y: 0 }, + player: "", + }, + PositionPlayerValue: { + player: "", + }, + Snake: { + owner: "", + length: 0, + first_segment_id: 0, + last_segment_id: 0, + direction: new CairoCustomEnum({ + None: "", + Left: undefined, + Right: undefined, + Up: undefined, + Down: undefined, + }), + color: 0, + text: 0, + is_dying: false, + }, + SnakeSegment: { + id: 0, + previous_id: 0, + next_id: 0, + position: { x: 0, y: 0 }, + pixel_original_color: 0, + pixel_original_text: 0, + pixel_original_app: "", + }, + SnakeSegmentValue: { + previous_id: 0, + next_id: 0, + position: { x: 0, y: 0 }, + pixel_original_color: 0, + pixel_original_text: 0, + pixel_original_app: "", + }, + SnakeValue: { + length: 0, + first_segment_id: 0, + last_segment_id: 0, + direction: new CairoCustomEnum({ + None: "", + Left: undefined, + Right: undefined, + Up: undefined, + Down: undefined, + }), + color: 0, + text: 0, + is_dying: false, + }, + Area: { + id: 0, + app: "", + owner: "", + color: 0, + }, + AreaValue: { + app: "", + owner: "", + color: 0, + }, + RTree: { + id: 0, + children: 0, + }, + RTreeValue: { + children: 0, + }, + Dummy: { + id: 0, + defaultParams: { + player_override: new CairoOption(CairoOptionVariant.None), + system_override: new CairoOption(CairoOptionVariant.None), + area_hint: new CairoOption(CairoOptionVariant.None), + position: { x: 0, y: 0 }, + color: 0, + }, + bounds: { x_min: 0, y_min: 0, x_max: 0, y_max: 0 }, + pixelUpdate: { + position: { x: 0, y: 0 }, + color: new CairoOption(CairoOptionVariant.None), + owner: new CairoOption(CairoOptionVariant.None), + app: new CairoOption(CairoOptionVariant.None), + text: new CairoOption(CairoOptionVariant.None), + timestamp: new CairoOption(CairoOptionVariant.None), + action: new CairoOption(CairoOptionVariant.None), + }, + emoji: { value: 0 }, + }, + DummyValue: { + defaultParams: { + player_override: new CairoOption(CairoOptionVariant.None), + system_override: new CairoOption(CairoOptionVariant.None), + area_hint: new CairoOption(CairoOptionVariant.None), + position: { x: 0, y: 0 }, + color: 0, + }, + bounds: { x_min: 0, y_min: 0, x_max: 0, y_max: 0 }, + pixelUpdate: { + position: { x: 0, y: 0 }, + color: new CairoOption(CairoOptionVariant.None), + owner: new CairoOption(CairoOptionVariant.None), + app: new CairoOption(CairoOptionVariant.None), + text: new CairoOption(CairoOptionVariant.None), + timestamp: new CairoOption(CairoOptionVariant.None), + action: new CairoOption(CairoOptionVariant.None), + }, + emoji: { value: 0 }, + }, + Pixel: { + position: { x: 0, y: 0 }, + app: "", + color: 0, + created_at: 0, + updated_at: 0, + timestamp: 0, + owner: "", + text: 0, + action: 0, + }, + PixelUpdate: { + position: { x: 0, y: 0 }, + color: new CairoOption(CairoOptionVariant.None), + owner: new CairoOption(CairoOptionVariant.None), + app: new CairoOption(CairoOptionVariant.None), + text: new CairoOption(CairoOptionVariant.None), + timestamp: new CairoOption(CairoOptionVariant.None), + action: new CairoOption(CairoOptionVariant.None), + }, + PixelValue: { + app: "", + color: 0, + created_at: 0, + updated_at: 0, + timestamp: 0, + owner: "", + text: 0, + action: 0, + }, + QueueItem: { + id: 0, + valid: false, + }, + QueueItemValue: { + valid: false, + }, + App: { + system: "", + name: 0, + icon: 0, + action: 0, + }, + AppName: { + name: 0, + system: "", + }, + AppNameValue: { + system: "", + }, + AppUser: { + system: "", + player: "", + action: 0, + }, + AppUserValue: { + action: 0, + }, + AppValue: { + name: 0, + icon: 0, + action: 0, + }, + CoreActionsAddress: { + key: 0, + value: "", + }, + CoreActionsAddressValue: { + value: "", + }, + Bounds: { + x_min: 0, + y_min: 0, + x_max: 0, + y_max: 0, + }, + DefaultParameters: { + player_override: new CairoOption(CairoOptionVariant.None), + system_override: new CairoOption(CairoOptionVariant.None), + area_hint: new CairoOption(CairoOptionVariant.None), + position: { x: 0, y: 0 }, + color: 0, + }, + Emoji: { + value: 0, + }, + Position: { + x: 0, + y: 0, + }, + Notification: { + position: { x: 0, y: 0 }, + app: "", + color: 0, + from: new CairoOption(CairoOptionVariant.None), + to: new CairoOption(CairoOptionVariant.None), + text: 0, + }, + NotificationValue: { + app: "", + color: 0, + from: new CairoOption(CairoOptionVariant.None), + to: new CairoOption(CairoOptionVariant.None), + text: 0, + }, + QueueScheduled: { + id: 0, + timestamp: 0, + called_system: "", + selector: 0, + calldata: [0], + }, + QueueScheduledValue: { + timestamp: 0, + called_system: "", + selector: 0, + calldata: [0], + }, + }, +}; export enum ModelsMapping { - Player = "pixelaw-Player", - PlayerValue = "pixelaw-PlayerValue", - PositionPlayer = "pixelaw-PositionPlayer", - PositionPlayerValue = "pixelaw-PositionPlayerValue", - Snake = "pixelaw-Snake", - SnakeSegment = "pixelaw-SnakeSegment", - SnakeSegmentValue = "pixelaw-SnakeSegmentValue", - SnakeValue = "pixelaw-SnakeValue", - Area = "pixelaw-Area", - AreaValue = "pixelaw-AreaValue", - RTree = "pixelaw-RTree", - RTreeValue = "pixelaw-RTreeValue", - Dummy = "pixelaw-Dummy", - DummyValue = "pixelaw-DummyValue", - Pixel = "pixelaw-Pixel", - PixelUpdate = "pixelaw-PixelUpdate", - PixelValue = "pixelaw-PixelValue", - QueueItem = "pixelaw-QueueItem", - QueueItemValue = "pixelaw-QueueItemValue", - App = "pixelaw-App", - AppName = "pixelaw-AppName", - AppNameValue = "pixelaw-AppNameValue", - AppUser = "pixelaw-AppUser", - AppUserValue = "pixelaw-AppUserValue", - AppValue = "pixelaw-AppValue", - CoreActionsAddress = "pixelaw-CoreActionsAddress", - CoreActionsAddressValue = "pixelaw-CoreActionsAddressValue", - Bounds = "pixelaw-Bounds", - DefaultParameters = "pixelaw-DefaultParameters", - Direction = "pixelaw-Direction", - Emoji = "pixelaw-Emoji", - Position = "pixelaw-Position", - Notification = "pixelaw-Notification", - NotificationValue = "pixelaw-NotificationValue", - QueueScheduled = "pixelaw-QueueScheduled", - QueueScheduledValue = "pixelaw-QueueScheduledValue", + Player = "pixelaw-Player", + PlayerValue = "pixelaw-PlayerValue", + PositionPlayer = "pixelaw-PositionPlayer", + PositionPlayerValue = "pixelaw-PositionPlayerValue", + Snake = "pixelaw-Snake", + SnakeSegment = "pixelaw-SnakeSegment", + SnakeSegmentValue = "pixelaw-SnakeSegmentValue", + SnakeValue = "pixelaw-SnakeValue", + Area = "pixelaw-Area", + AreaValue = "pixelaw-AreaValue", + RTree = "pixelaw-RTree", + RTreeValue = "pixelaw-RTreeValue", + Dummy = "pixelaw-Dummy", + DummyValue = "pixelaw-DummyValue", + Pixel = "pixelaw-Pixel", + PixelUpdate = "pixelaw-PixelUpdate", + PixelValue = "pixelaw-PixelValue", + QueueItem = "pixelaw-QueueItem", + QueueItemValue = "pixelaw-QueueItemValue", + App = "pixelaw-App", + AppName = "pixelaw-AppName", + AppNameValue = "pixelaw-AppNameValue", + AppUser = "pixelaw-AppUser", + AppUserValue = "pixelaw-AppUserValue", + AppValue = "pixelaw-AppValue", + CoreActionsAddress = "pixelaw-CoreActionsAddress", + CoreActionsAddressValue = "pixelaw-CoreActionsAddressValue", + Bounds = "pixelaw-Bounds", + DefaultParameters = "pixelaw-DefaultParameters", + Direction = "pixelaw-Direction", + Emoji = "pixelaw-Emoji", + Position = "pixelaw-Position", + Notification = "pixelaw-Notification", + NotificationValue = "pixelaw-NotificationValue", + QueueScheduled = "pixelaw-QueueScheduled", + QueueScheduledValue = "pixelaw-QueueScheduledValue", } diff --git a/packages/core-dojo/src/index.ts b/packages/core-dojo/src/index.ts index b4d67f1..9ef59de 100644 --- a/packages/core-dojo/src/index.ts +++ b/packages/core-dojo/src/index.ts @@ -1,5 +1,5 @@ -export * from "./DojoEngine.ts" -export * from "./DojoWallet.ts" +export * from "./DojoEngine.ts"; +export * from "./DojoWallet.ts"; // export * from "./generated/contracts.gen.ts" -export * from "./generated/models.gen.ts" -export * from "./types.ts" +export * from "./generated/models.gen.ts"; +export * from "./types.ts"; diff --git a/packages/core-dojo/src/interaction/Instruction.ts b/packages/core-dojo/src/interaction/Instruction.ts index e5f4b11..5874945 100644 --- a/packages/core-dojo/src/interaction/Instruction.ts +++ b/packages/core-dojo/src/interaction/Instruction.ts @@ -1,124 +1,140 @@ -import {poseidonHashMany} from "@scure/starknet" // TODO: change SALT to a dynamic constant -import type {AbiType, Coordinate, EnumType, ParamDefinitionType} from "./types.ts" +import { poseidonHashMany } from "@scure/starknet"; // TODO: change SALT to a dynamic constant +import type { + AbiType, + Coordinate, + EnumType, + ParamDefinitionType, +} from "./types.ts"; // TODO: change SALT to a dynamic constant -const SALT = 12345 +const SALT = 12345; -const PREFIX = "pixelaw" +const PREFIX = "pixelaw"; -const setStorage = (appName: string, paramName: string, position: Coordinate, value: Record) => { - const storageKey = `${PREFIX}::${appName}::${position[0]}::${position[1]}::${paramName}` - localStorage.setItem(storageKey, JSON.stringify(value)) -} +const setStorage = ( + appName: string, + paramName: string, + position: Coordinate, + value: Record, +) => { + const storageKey = `${PREFIX}::${appName}::${position[0]}::${position[1]}::${paramName}`; + localStorage.setItem(storageKey, JSON.stringify(value)); +}; -const getStorage = (appName: string, paramName: string, position: { x: number; y: number }, key: string) => { - const storageKey = `${PREFIX}::${appName}::${position.x}::${position.y}::${paramName}` - const storageItem = localStorage.getItem(storageKey) - if (!storageItem) return null +const getStorage = ( + appName: string, + paramName: string, + position: { x: number; y: number }, + key: string, +) => { + const storageKey = `${PREFIX}::${appName}::${position.x}::${position.y}::${paramName}`; + const storageItem = localStorage.getItem(storageKey); + if (!storageItem) return null; - const parsedItem = JSON.parse(storageItem) - return parsedItem[key] as number -} + const parsedItem = JSON.parse(storageItem); + return parsedItem[key] as number; +}; const isPrimitive = (type: string) => { - return ( - type === "u8" || - type === "u16" || - type === "u32" || - type === "u64" || - type === "u128" || - type === "u256" || - type === "usize" || - type === "bool" || - type === "felt252" - ) -} + return ( + type === "u8" || + type === "u16" || + type === "u32" || + type === "u64" || + type === "u128" || + type === "u256" || + type === "usize" || + type === "bool" || + type === "felt252" + ); +}; export const isInstruction = (paramName: string) => { - const [instruction, ...otherValues] = paramName.split("_") - switch (instruction) { - case "cr": - return otherValues.length === 2 - case "rv": - case "rs": - return otherValues.length === 1 - default: - return false - } -} + const [instruction, ...otherValues] = paramName.split("_"); + switch (instruction) { + case "cr": + return otherValues.length === 2; + case "rv": + case "rs": + return otherValues.length === 1; + default: + return false; + } +}; type InterpretType = ( - appName: string, - position: Coordinate, - typeInstruction: string, - abi: AbiType, -) => ParamDefinitionType + appName: string, + position: Coordinate, + typeInstruction: string, + abi: AbiType, +) => ParamDefinitionType; export const interpret: InterpretType = ( - appName: string, - position: Coordinate, - typeInstruction: string, - abi: AbiType, + appName: string, + position: Coordinate, + typeInstruction: string, + abi: AbiType, ) => { - const [instruction, ...otherValues] = typeInstruction.split("_") - switch (instruction) { - case "cr": { - let finalizedType: "number" | "string" | "enum" = "number" - const [type, name] = otherValues - let variants: { name: string; value: number }[] = [] - if (!isPrimitive(type)) { - // for now assuming that all nonPrimitives are enums - const typeDefinition: EnumType | undefined = abi.find( - (typeDef) => typeDef.name.includes(type) && typeDef.type === "enum", - ) as unknown as EnumType | undefined - if (!typeDefinition) throw new Error(`unknown type definition: ${type}`) - variants = typeDefinition.variants - .map((variant, index) => { - return { - name: variant.name, - value: index, - } - }) - .filter((variant) => variant.name !== "None") - finalizedType = "enum" - } else if (type === "felt252") finalizedType = "string" - return { - value: undefined, - name, - transformValue: (value: number) => { - setStorage(appName, name, position, { value, salt: SALT }) - return poseidonHashMany([BigInt(value), BigInt(SALT)]) - }, - variants, - type: finalizedType, - } - } - - case "rv": { - const [name] = otherValues + const [instruction, ...otherValues] = typeInstruction.split("_"); + switch (instruction) { + case "cr": { + let finalizedType: "number" | "string" | "enum" = "number"; + const [type, name] = otherValues; + let variants: { name: string; value: number }[] = []; + if (!isPrimitive(type)) { + // for now assuming that all nonPrimitives are enums + const typeDefinition: EnumType | undefined = abi.find( + (typeDef) => typeDef.name.includes(type) && typeDef.type === "enum", + ) as unknown as EnumType | undefined; + if (!typeDefinition) + throw new Error(`unknown type definition: ${type}`); + variants = typeDefinition.variants + .map((variant, index) => { return { - transformValue: undefined, - name, - variants: [], - type: "number", - value: getStorage(appName, name, position, "value") ?? 0, - } - } + name: variant.name, + value: index, + }; + }) + .filter((variant) => variant.name !== "None"); + finalizedType = "enum"; + } else if (type === "felt252") finalizedType = "string"; + return { + value: undefined, + name, + transformValue: (value: number) => { + setStorage(appName, name, position, { value, salt: SALT }); + return poseidonHashMany([BigInt(value), BigInt(SALT)]); + }, + variants, + type: finalizedType, + }; + } - case "rs": { - const [name] = otherValues - return { - transformValue: undefined, - name, - variants: [], - type: "number", - value: getStorage(appName, name, position, "salt") ?? 0, - } - } + case "rv": { + const [name] = otherValues; + return { + transformValue: undefined, + name, + variants: [], + type: "number", + value: getStorage(appName, name, position, "value") ?? 0, + }; + } - default: - throw new Error(`unknown instruction: ${typeInstruction}`) + case "rs": { + const [name] = otherValues; + return { + transformValue: undefined, + name, + variants: [], + type: "number", + value: getStorage(appName, name, position, "salt") ?? 0, + }; } -} -export default interpret + default: + throw new Error(`unknown instruction: ${typeInstruction}`); + } +}; + +export default interpret; diff --git a/packages/core-dojo/src/interaction/generateDojoCall.ts b/packages/core-dojo/src/interaction/generateDojoCall.ts index 5ddae88..acbc705 100644 --- a/packages/core-dojo/src/interaction/generateDojoCall.ts +++ b/packages/core-dojo/src/interaction/generateDojoCall.ts @@ -1,34 +1,41 @@ -import { getContractByName, type Manifest } from "@dojoengine/core" -import { NAMESPACE, type InteractParam, type Position } from "@pixelaw/core" -import type { Call } from "starknet" +import { getContractByName, type Manifest } from "@dojoengine/core"; +import { NAMESPACE, type InteractParam, type Position } from "@pixelaw/core"; +import type { Call } from "starknet"; export default function generateDojoCall( - manifest: Manifest, - params: InteractParam[], - contractName: string, - action: string, - position: Position, - color: number, + manifest: Manifest, + params: InteractParam[], + contractName: string, + action: string, + position: Position, + color: number, ): Call { - const contract = getContractByName(manifest, NAMESPACE, contractName) + const contract = getContractByName(manifest, NAMESPACE, contractName); - const player_override = 1 - const system_override = 1 - const area_hint = 1 - const position_x = position.x - const position_y = position.y + const player_override = 1; + const system_override = 1; + const area_hint = 1; + const position_x = position.x; + const position_y = position.y; - const calldata = [player_override, system_override, area_hint, position_x, position_y, color] + const calldata = [ + player_override, + system_override, + area_hint, + position_x, + position_y, + color, + ]; - for (const param of params) { - if (param.value) { - calldata.push(param.value) - } + for (const param of params) { + if (param.value) { + calldata.push(param.value); } + } - return { - contractAddress: contract.address, - entrypoint: action, - calldata, - } + return { + contractAddress: contract.address, + entrypoint: action, + calldata, + }; } diff --git a/packages/core-dojo/src/interaction/params.ts b/packages/core-dojo/src/interaction/params.ts index b9acd20..c855507 100644 --- a/packages/core-dojo/src/interaction/params.ts +++ b/packages/core-dojo/src/interaction/params.ts @@ -1,166 +1,187 @@ -import type { Manifest } from "@dojoengine/core" -import type { InteractParam, InteractParams, Position } from "@pixelaw/core" -import { poseidonHashMany } from "@scure/starknet" -import type { DojoInteraction } from "../DojoInteraction.ts" -import { convertTextToHex, generateRandomFelt252 } from "../utils/utils.ts" -import type { InterfaceType } from "./types.ts" -import type { DojoEngine } from "../DojoEngine.ts" +import type { Manifest } from "@dojoengine/core"; +import type { InteractParam, InteractParams, Position } from "@pixelaw/core"; +import { poseidonHashMany } from "@scure/starknet"; +import type { DojoInteraction } from "../DojoInteraction.ts"; +import { convertTextToHex, generateRandomFelt252 } from "../utils/utils.ts"; +import type { InterfaceType } from "./types.ts"; +import type { DojoEngine } from "../DojoEngine.ts"; -const DEFAULT_PARAMETERS_TYPE = "pixelaw::core::utils::DefaultParameters" +const DEFAULT_PARAMETERS_TYPE = "pixelaw::core::utils::DefaultParameters"; export const isPrimitive = (type: string) => { - return ( - type === "core::u8" || - type === "core::u16" || - type === "core::u32" || - type === "core::u64" || - type === "core::u128" || - type === "core::u256" || - type === "core::bool" || - type === "core::felt252" - ) -} + return ( + type === "core::u8" || + type === "core::u16" || + type === "core::u32" || + type === "core::u64" || + type === "core::u128" || + type === "core::u256" || + type === "core::bool" || + type === "core::felt252" + ); +}; export const convertSnakeToPascal = (snakeCaseString: string) => { - return snakeCaseString - .split("_") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join("") -} + return snakeCaseString + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(""); +}; export function findContract(manifest: Manifest, contractName: string) { - return manifest.contracts.find((c) => c.name.includes(contractName)) + return manifest.contracts.find((c) => c.name.includes(contractName)); } -export function findInterface(abi: any[], interfaceName: string, strict?: boolean): InterfaceType | undefined { - const methods = abi.find((x) => x.type === "interface" && x.name.includes(interfaceName)) as - | InterfaceType - | undefined - if (!methods && strict) throw new Error(`unknown interface: ${interfaceName}`) - return methods +export function findInterface( + abi: any[], + interfaceName: string, + strict?: boolean, +): InterfaceType | undefined { + const methods = abi.find( + (x) => x.type === "interface" && x.name.includes(interfaceName), + ) as InterfaceType | undefined; + if (!methods && strict) + throw new Error(`unknown interface: ${interfaceName}`); + return methods; } -export function findFunctionDefinition(abi, interfaceName, methodName: string, strict = false) { - const methods = findInterface(abi, interfaceName, strict) - if (!methods?.items) { - if (strict) throw new Error(`no methods for interface: ${interfaceName}`) - return [] - } - - let functionDef = methods.items.find((method) => method.name === methodName && method.type === "function") - if (!functionDef) { - functionDef = methods.items.find((method) => method.name === "interact" && method.type === "function") - if (!functionDef && strict) throw new Error(`function ${methodName} not found`) - } - return functionDef +export function findFunctionDefinition( + abi, + interfaceName, + methodName: string, + strict = false, +) { + const methods = findInterface(abi, interfaceName, strict); + if (!methods?.items) { + if (strict) throw new Error(`no methods for interface: ${interfaceName}`); + return []; + } + + let functionDef = methods.items.find( + (method) => method.name === methodName && method.type === "function", + ); + if (!functionDef) { + functionDef = methods.items.find( + (method) => method.name === "interact" && method.type === "function", + ); + if (!functionDef && strict) + throw new Error(`function ${methodName} not found`); + } + return functionDef; } export function extractParameters(functionDef: any) { - const parameters = [] - for (const input of functionDef.inputs) { - if (input.type !== DEFAULT_PARAMETERS_TYPE) { - parameters.push(input) - } + const parameters = []; + for (const input of functionDef.inputs) { + if (input.type !== DEFAULT_PARAMETERS_TYPE) { + parameters.push(input); } - return parameters + } + return parameters; } export async function prepareParams( - interaction: DojoInteraction, - rawParams: InteractParam[], - abi: any[], + interaction: DojoInteraction, + rawParams: InteractParam[], + abi: any[], ): Promise { - const result: InteractParam[] = [] - const storage = interaction.engine.core.storage - const address = interaction.engine.core.wallet?.address - const positionString = `${interaction.position.x}_${interaction.position.y}` - for (const rawParam of rawParams) { - const param = { ...rawParam } - - const [nameFirst, ...nameRemaining] = rawParam.name.split("_") - - // Check if the name as a prefix - if (nameRemaining.length > 0) { - if (nameFirst === "crc") { - // TODO check that nameRemaining has 2 elements, for varname and vartype - param.name = nameRemaining[0] - // @ts-ignore TODO - param.type = nameRemaining[1] - - // setup a "transformer" that, after choosing a value, encodes it and calls the right function name. - param.transformer = async () => { - const salt = `0x${generateRandomFelt252().toString(16)}` - // Store the original values - await storage.setItem(`param_${address}-${positionString}-${param.name}`, param.value) - await storage.setItem(`param_${address}-${positionString}-${param.name}-salt`, salt) - param.name = rawParam.name - param.type = rawParam.type - param.variants = rawParam.variants - - // @ts-ignore TODO - param.value = `0x${poseidonHashMany([BigInt(param.value), BigInt(salt)]).toString(16)}` - console.log("hash:", param.value) - console.log("salt:", salt) - } - } else if (nameFirst === "crv") { - // TODO check that nameRemaining has 1 elements, for varname - const originalParamName = nameRemaining[0] - - // TODO this param does not require user input, but is read from storage - const origValue = await storage.getItem( - `param_${address}-${positionString}-${originalParamName}`, - ) - - param.value = origValue - param.systemOnly = true - } else if (nameFirst === "crs") { - // TODO check that nameRemaining has 1 elements, for varname - // param.name = nameRemaining[0] - - // this param does not require user input, but is read from storage - const salt = await storage.getItem( - `param_${address}-${positionString}-${nameRemaining[0]}-salt`, - ) - param.value = salt - console.log("saltout:", param.value) - param.systemOnly = true - } else { - // Nothing, the name just had underscores but no special prefix - } - } + const result: InteractParam[] = []; + const storage = interaction.engine.core.storage; + const address = interaction.engine.core.wallet?.address; + const positionString = `${interaction.position.x}_${interaction.position.y}`; + for (const rawParam of rawParams) { + const param = { ...rawParam }; + + const [nameFirst, ...nameRemaining] = rawParam.name.split("_"); + + // Check if the name as a prefix + if (nameRemaining.length > 0) { + if (nameFirst === "crc") { + // TODO check that nameRemaining has 2 elements, for varname and vartype + param.name = nameRemaining[0]; + // @ts-ignore TODO + param.type = nameRemaining[1]; + + // setup a "transformer" that, after choosing a value, encodes it and calls the right function name. + param.transformer = async () => { + const salt = `0x${generateRandomFelt252().toString(16)}`; + // Store the original values + await storage.setItem( + `param_${address}-${positionString}-${param.name}`, + param.value, + ); + await storage.setItem( + `param_${address}-${positionString}-${param.name}-salt`, + salt, + ); + param.name = rawParam.name; + param.type = rawParam.type; + param.variants = rawParam.variants; + + // @ts-ignore TODO + param.value = `0x${poseidonHashMany([BigInt(param.value), BigInt(salt)]).toString(16)}`; + console.log("hash:", param.value); + console.log("salt:", salt); + }; + } else if (nameFirst === "crv") { + // TODO check that nameRemaining has 1 elements, for varname + const originalParamName = nameRemaining[0]; + + // TODO this param does not require user input, but is read from storage + const origValue = await storage.getItem( + `param_${address}-${positionString}-${originalParamName}`, + ); + + param.value = origValue; + param.systemOnly = true; + } else if (nameFirst === "crs") { + // TODO check that nameRemaining has 1 elements, for varname + // param.name = nameRemaining[0] + + // this param does not require user input, but is read from storage + const salt = await storage.getItem( + `param_${address}-${positionString}-${nameRemaining[0]}-salt`, + ); + param.value = salt; + console.log("saltout:", param.value); + param.systemOnly = true; + } else { + // Nothing, the name just had underscores but no special prefix + } + } - param.variants = [] - // const transformer = undefined - - if (!isPrimitive(param.type)) { - // If the type is not a primitive, let's look for an Enum with this name - const typeDefinition = abi.find((x) => { - return x.type === "enum" && x.name.endsWith(param.type) - }) - if (typeDefinition?.type === "enum") { - for (const index in typeDefinition.variants) { - const variant = typeDefinition.variants[index] - if (variant.name !== "None") { - param.variants.push({ - name: variant.name, - value: Number.parseInt(index), - }) - } - } - param.type = "enum" - } else { - // @ts-ignore pre-processing - if (param.type === "pixelaw::core::utils::Emoji") { - param.type = "emoji" - param.transformer = async () => { - param.value = convertTextToHex(param.value as string) - } - } - } - } else if (param.type === "core::felt252") { - param.type = "string" + param.variants = []; + // const transformer = undefined + + if (!isPrimitive(param.type)) { + // If the type is not a primitive, let's look for an Enum with this name + const typeDefinition = abi.find((x) => { + return x.type === "enum" && x.name.endsWith(param.type); + }); + if (typeDefinition?.type === "enum") { + for (const index in typeDefinition.variants) { + const variant = typeDefinition.variants[index]; + if (variant.name !== "None") { + param.variants.push({ + name: variant.name, + value: Number.parseInt(index), + }); + } + } + param.type = "enum"; + } else { + // @ts-ignore pre-processing + if (param.type === "pixelaw::core::utils::Emoji") { + param.type = "emoji"; + param.transformer = async () => { + param.value = convertTextToHex(param.value as string); + }; } - result.push(param) + } + } else if (param.type === "core::felt252") { + param.type = "string"; } - return result + result.push(param); + } + return result; } diff --git a/packages/core-dojo/src/interaction/types.ts b/packages/core-dojo/src/interaction/types.ts index 84e79e0..84e8504 100644 --- a/packages/core-dojo/src/interaction/types.ts +++ b/packages/core-dojo/src/interaction/types.ts @@ -1,148 +1,148 @@ -import type { Pixel } from "../generated/models.gen" +import type { Pixel } from "../generated/models.gen"; export enum Active_Page { - Home = 0, - Network = 1, - Lobby = 2, - Gameplay = 3, + Home = 0, + Network = 1, + Lobby = 2, + Gameplay = 3, } export type MainLayoutType = { - setHasNavbar: React.Dispatch> - setHasBackgroundImage: React.Dispatch> - setHasBackgroundOverlay: React.Dispatch> - currentPage: number - setCurrentPage: React.Dispatch> -} + setHasNavbar: React.Dispatch>; + setHasBackgroundImage: React.Dispatch>; + setHasBackgroundOverlay: React.Dispatch>; + currentPage: number; + setCurrentPage: React.Dispatch>; +}; export type Account = { - address: string - active: boolean -} + address: string; + active: boolean; +}; export type PositionWithAddressAndType = { - x: number | undefined - y: number | undefined - address?: string | number - pixel?: string | number -} + x: number | undefined; + y: number | undefined; + address?: string | number; + pixel?: string | number; +}; export type NotificationDataType = { - x: number - y: number - pixelType?: string | number -} + x: number; + y: number; + pixelType?: string | number; +}; export type TPackedSQLPixel = { - t: string - v: number - c: string -} + t: string; + v: number; + c: string; +}; -export type TPixel = Omit +export type TPixel = Omit; /// Manifest types type ImplType = { - type: "impl" - name: string - interface_name: string -} + type: "impl"; + name: string; + interface_name: string; +}; type BaseType = { - name: string - type: string -} + name: string; + type: string; +}; type FunctionType = { - type: "function" - name: string - inputs: BaseType[] - outputs: { type: string }[] - state_mutability: "external" | "view" -} + type: "function"; + name: string; + inputs: BaseType[]; + outputs: { type: string }[]; + state_mutability: "external" | "view"; +}; export type InterfaceType = { - type: "interface" - name: string - items: FunctionType[] -} + type: "interface"; + name: string; + items: FunctionType[]; +}; type StructType = { - type: "struct" - name: string - members: BaseType[] -} + type: "struct"; + name: string; + members: BaseType[]; +}; export type EnumType = { - type: "enum" - name: string - variants: BaseType[] -} + type: "enum"; + name: string; + variants: BaseType[]; +}; type EventMember = { - name: string - type: string - kind: string -} + name: string; + type: string; + kind: string; +}; type EventStructType = { - type: "event" - name: string - kind: "struct" - members: EventMember[] -} + type: "event"; + name: string; + kind: "struct"; + members: EventMember[]; +}; type EventEnumType = { - type: "event" - name: string - kind: "enum" - variants: EventMember[] -} + type: "event"; + name: string; + kind: "enum"; + variants: EventMember[]; +}; export type AbiType = ( - | ImplType - | InterfaceType - | StructType - | EnumType - | FunctionType - | EventStructType - | EventEnumType -)[] + | ImplType + | InterfaceType + | StructType + | EnumType + | FunctionType + | EventStructType + | EventEnumType +)[]; type ComputedValueEntryPoint = { - contract: string - entrypoint: string - model?: string -} + contract: string; + entrypoint: string; + model?: string; +}; type Contract = { - name: string - address?: string - class_hash: string - abi: AbiType - reads: string[] - writes: string[] - computed: ComputedValueEntryPoint[] -} + name: string; + address?: string; + class_hash: string; + abi: AbiType; + reads: string[]; + writes: string[]; + computed: ComputedValueEntryPoint[]; +}; type Class = { - name: string - class_hash: string - abi: AbiType -} + name: string; + class_hash: string; + abi: AbiType; +}; type Member = { - name: string - type: string - key: boolean -} + name: string; + type: string; + key: boolean; +}; type Model = { - name: string - members: Member[] - class_hash: string - abi: AbiType -} + name: string; + members: Member[]; + class_hash: string; + abi: AbiType; +}; // export type Manifest = { // world: Contract diff --git a/packages/core-dojo/src/types.ts b/packages/core-dojo/src/types.ts index 605a155..720a735 100644 --- a/packages/core-dojo/src/types.ts +++ b/packages/core-dojo/src/types.ts @@ -1,24 +1,24 @@ -import type { WalletConfig } from "@pixelaw/core" +import type { WalletConfig } from "@pixelaw/core"; export interface DojoConfig { - serverUrl: string - rpcUrl: string - toriiUrl: string - relayUrl: string - feeTokenAddress: string - wallets: { - burner?: WalletConfig - controller?: WalletConfig - } - world: string + serverUrl: string; + rpcUrl: string; + toriiUrl: string; + relayUrl: string; + feeTokenAddress: string; + wallets: { + burner?: WalletConfig; + controller?: WalletConfig; + }; + world: string; } export interface SimpleContract { - kind: string - address: string - abi: any - tag: string - name?: string + kind: string; + address: string; + abi: any; + tag: string; + name?: string; } -export const ENGINE_ID = "dojo" +export const ENGINE_ID = "dojo"; diff --git a/packages/core-dojo/src/utils/CartridgeController.ts b/packages/core-dojo/src/utils/CartridgeController.ts index 13530c4..a965e31 100644 --- a/packages/core-dojo/src/utils/CartridgeController.ts +++ b/packages/core-dojo/src/utils/CartridgeController.ts @@ -1,21 +1,23 @@ -import { ControllerConnector } from "@cartridge/connector" +import { ControllerConnector } from "@cartridge/connector"; -import type { ControllerOptions } from "@cartridge/controller" -import { getContractByName } from "@dojoengine/core" -import type { Connector } from "@starknet-react/core" -import { cartridgeProvider } from "@starknet-react/core" +import type { ControllerOptions } from "@cartridge/controller"; +import { getContractByName } from "@dojoengine/core"; +import type { Connector } from "@starknet-react/core"; +import { cartridgeProvider } from "@starknet-react/core"; -import { shortString } from "starknet" +import { shortString } from "starknet"; -const policies = [] +const policies = []; const options: ControllerOptions = { - rpc: cartridgeProvider().nodeUrl, - policies, - // theme: "dope-wars", - // colorMode: "light" -} + rpc: cartridgeProvider().nodeUrl, + policies, + // theme: "dope-wars", + // colorMode: "light" +}; -const cartridgeConnector = new ControllerConnector(options) as never as Connector +const cartridgeConnector = new ControllerConnector( + options, +) as never as Connector; -export default cartridgeConnector +export default cartridgeConnector; diff --git a/packages/core-dojo/src/utils/controller.ts b/packages/core-dojo/src/utils/controller.ts index 7e9f756..40fe6d0 100644 --- a/packages/core-dojo/src/utils/controller.ts +++ b/packages/core-dojo/src/utils/controller.ts @@ -1,103 +1,107 @@ -import ControllerConnector from "@cartridge/connector/controller" -import type { SessionPolicies } from "@cartridge/controller" -import type { Manifest } from "@dojoengine/core" -import { shortString } from "starknet" +import ControllerConnector from "@cartridge/connector/controller"; +import type { SessionPolicies } from "@cartridge/controller"; +import type { Manifest } from "@dojoengine/core"; +import { shortString } from "starknet"; interface ConnectorParams { - rpcUrl: string - feeTokenAddress: string - manifest: Manifest + rpcUrl: string; + feeTokenAddress: string; + manifest: Manifest; } -export const getControllerConnector = ({ rpcUrl, feeTokenAddress, manifest }: ConnectorParams): ControllerConnector => { - const contracts = {} - const messages = [ - { - name: "PixelAW Message Signing", - description: "Allows signing messages for Pixelaw", - types: { - StarknetDomain: [ - { name: "name", type: "shortstring" }, - { name: "version", type: "shortstring" }, - { name: "chainId", type: "shortstring" }, - { name: "revision", type: "shortstring" }, - ], - "pixelaw-Position": [ - { name: "x", type: "u16" }, - { name: "y", type: "u16" }, - ], - "pixelaw-Pixel": [ - { name: "position", type: "pixelaw-Position" }, - { name: "action", type: "shortstring" }, - { name: "color", type: "shortstring" }, - { name: "owner", type: "shortstring" }, - { name: "text", type: "shortstring" }, - { name: "timestamp", type: "shortstring" }, - { name: "app", type: "shortstring" }, - ], - }, - primaryType: "pixelaw-Pixel", - domain: { - name: "pixelaw", - version: "1", - chainId: "SN_SEPOLIA", - revision: "1", - }, - }, - ] - - contracts[manifest.contracts[0].address] = { - name: manifest.contracts[0].tag, - description: "", - methods: [ - { - name: "process_queue", - description: "Process Queue item", - entrypoint: "process_queue", - }, +export const getControllerConnector = ({ + rpcUrl, + feeTokenAddress, + manifest, +}: ConnectorParams): ControllerConnector => { + const contracts = {}; + const messages = [ + { + name: "PixelAW Message Signing", + description: "Allows signing messages for Pixelaw", + types: { + StarknetDomain: [ + { name: "name", type: "shortstring" }, + { name: "version", type: "shortstring" }, + { name: "chainId", type: "shortstring" }, + { name: "revision", type: "shortstring" }, + ], + "pixelaw-Position": [ + { name: "x", type: "u16" }, + { name: "y", type: "u16" }, + ], + "pixelaw-Pixel": [ + { name: "position", type: "pixelaw-Position" }, + { name: "action", type: "shortstring" }, + { name: "color", type: "shortstring" }, + { name: "owner", type: "shortstring" }, + { name: "text", type: "shortstring" }, + { name: "timestamp", type: "shortstring" }, + { name: "app", type: "shortstring" }, ], - } + }, + primaryType: "pixelaw-Pixel", + domain: { + name: "pixelaw", + version: "1", + chainId: "SN_SEPOLIA", + revision: "1", + }, + }, + ]; - for (const contract of manifest.contracts) { - if (contract.name.length === 0) continue - contracts[contract.address] = { - name: contract.name, - description: "", - methods: [ - { - name: "interact", - description: `Interact with ${contract.name}`, - entrypoint: "interact", - }, - ], - } - } + contracts[manifest.contracts[0].address] = { + name: manifest.contracts[0].tag, + description: "", + methods: [ + { + name: "process_queue", + description: "Process Queue item", + entrypoint: "process_queue", + }, + ], + }; - const policies: SessionPolicies = { - // TODO for now not doing signed messages due to torii changes - // messages, - contracts, - } + for (const contract of manifest.contracts) { + if (contract.name.length === 0) continue; + contracts[contract.address] = { + name: contract.name, + description: "", + methods: [ + { + name: "interact", + description: `Interact with ${contract.name}`, + entrypoint: "interact", + }, + ], + }; + } - return new ControllerConnector({ - policies, - defaultChainId: shortString.encodeShortString("SN_SEPOLIA"), - chains: [ - { rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" }, - { rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet" }, - ], - propagateSessionErrors: true, - // profileUrl, - // slot: "pixelaw-slot", - // preset: "pixelaw", - namespace: "pixelaw", - // tokens: { - // erc20: [ - // // $LORDS - // // "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", - // // $FLIP - // // "0x01bfe97d729138fc7c2d93c77d6d1d8a24708d5060608017d9b384adf38f04c7", - // ], - // }, - }) -} + const policies: SessionPolicies = { + // TODO for now not doing signed messages due to torii changes + // messages, + contracts, + }; + + return new ControllerConnector({ + policies, + defaultChainId: shortString.encodeShortString("SN_SEPOLIA"), + chains: [ + { rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" }, + { rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet" }, + ], + propagateSessionErrors: true, + // profileUrl, + // slot: "pixelaw-slot", + // preset: "pixelaw", + namespace: "pixelaw", + // tokens: { + // erc20: [ + // // $LORDS + // // "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + // // $FLIP + // // "0x01bfe97d729138fc7c2d93c77d6d1d8a24708d5060608017d9b384adf38f04c7", + // ], + // }, + }); +}; diff --git a/packages/core-dojo/src/utils/customExecute.ts b/packages/core-dojo/src/utils/customExecute.ts index 81e360f..c43c2ea 100644 --- a/packages/core-dojo/src/utils/customExecute.ts +++ b/packages/core-dojo/src/utils/customExecute.ts @@ -1,48 +1,48 @@ import { - type Account, - type AllowArray, - type Call, - type UniversalDetails, - type InvokeFunctionResponse, - type InvocationsSignerDetails, - transaction, - stark, -} from "starknet" + type Account, + type AllowArray, + type Call, + type UniversalDetails, + type InvokeFunctionResponse, + type InvocationsSignerDetails, + transaction, + stark, +} from "starknet"; export async function customExecute( - this: Account, - transactions: AllowArray, - transactionsDetail?: UniversalDetails, + this: Account, + transactions: AllowArray, + transactionsDetail?: UniversalDetails, ): Promise { - const details = transactionsDetail || {} - const calls = Array.isArray(transactions) ? transactions : [transactions] + const details = transactionsDetail || {}; + const calls = Array.isArray(transactions) ? transactions : [transactions]; - const version = "0x3" // hardcode to v3 + const version = "0x3"; // hardcode to v3 - const chainId = await this.getChainId() + const chainId = await this.getChainId(); - const signerDetails: InvocationsSignerDetails = { - ...stark.v3Details(details), - walletAddress: this.address, - nonce: transactionsDetail.nonce, - version, - chainId, - cairoVersion: await this.getCairoVersion(), - } + const signerDetails: InvocationsSignerDetails = { + ...stark.v3Details(details), + walletAddress: this.address, + nonce: transactionsDetail.nonce, + version, + chainId, + cairoVersion: await this.getCairoVersion(), + }; - const signature = await this.signer.signTransaction(calls, signerDetails) + const signature = await this.signer.signTransaction(calls, signerDetails); - const calldata = transaction.fromCallsToExecuteCalldata_cairo1(calls) + const calldata = transaction.fromCallsToExecuteCalldata_cairo1(calls); - const response = await this.invokeFunction( - { contractAddress: this.address, calldata, signature }, - { - ...stark.v3Details(details), - nonce: transactionsDetail.nonce, - maxFee: 0n, - version, - }, - ) + const response = await this.invokeFunction( + { contractAddress: this.address, calldata, signature }, + { + ...stark.v3Details(details), + nonce: transactionsDetail.nonce, + maxFee: 0n, + version, + }, + ); - return response + return response; } diff --git a/packages/core-dojo/src/utils/manifest.ts b/packages/core-dojo/src/utils/manifest.ts index 7998025..416001b 100644 --- a/packages/core-dojo/src/utils/manifest.ts +++ b/packages/core-dojo/src/utils/manifest.ts @@ -2,3083 +2,3136 @@ // models for pixelaw 0.6.13 export function baseManifest(worldAddress: string) { - return { - world: { - class_hash: "0x7c9469d45a9cdbab775035afb48e1fa73fb35ab059fcb9dfb0a301aa973e783", - address: worldAddress, - seed: "pixelaw", - name: "pixelaw", - entrypoints: [ - "uuid", - "set_metadata", - "register_namespace", - "register_event", - "register_model", - "register_contract", - "register_library", - "init_contract", - "upgrade_event", - "upgrade_model", - "upgrade_contract", - "emit_event", - "emit_events", - "set_entity", - "set_entities", - "delete_entity", - "delete_entities", - "grant_owner", - "revoke_owner", - "grant_writer", - "revoke_writer", - "upgrade", - ], - abi: [ - { - type: "impl", - name: "World", - interface_name: "dojo::world::iworld::IWorld", - }, - { - type: "struct", - name: "core::byte_array::ByteArray", - members: [ - { - name: "data", - type: "core::array::Array::", - }, - { - name: "pending_word", - type: "core::felt252", - }, - { - name: "pending_word_len", - type: "core::integer::u32", - }, - ], - }, - { - type: "enum", - name: "dojo::world::resource::Resource", - variants: [ - { - name: "Model", - type: "(core::starknet::contract_address::ContractAddress, core::felt252)", - }, - { - name: "Event", - type: "(core::starknet::contract_address::ContractAddress, core::felt252)", - }, - { - name: "Contract", - type: "(core::starknet::contract_address::ContractAddress, core::felt252)", - }, - { - name: "Namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "World", - type: "()", - }, - { - name: "Unregistered", - type: "()", - }, - { - name: "Library", - type: "(core::starknet::class_hash::ClassHash, core::felt252)", - }, - ], - }, - { - type: "struct", - name: "dojo::model::metadata::ResourceMetadata", - members: [ - { - name: "resource_id", - type: "core::felt252", - }, - { - name: "metadata_uri", - type: "core::byte_array::ByteArray", - }, - { - name: "metadata_hash", - type: "core::felt252", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::>", - members: [ - { - name: "snapshot", - type: "@core::array::Array::>", - }, - ], - }, - { - type: "enum", - name: "dojo::model::definition::ModelIndex", - variants: [ - { - name: "Keys", - type: "core::array::Span::", - }, - { - name: "Id", - type: "core::felt252", - }, - { - name: "MemberId", - type: "(core::felt252, core::felt252)", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "struct", - name: "dojo::meta::layout::FieldLayout", - members: [ - { - name: "selector", - type: "core::felt252", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "enum", - name: "dojo::meta::layout::Layout", - variants: [ - { - name: "Fixed", - type: "core::array::Span::", - }, - { - name: "Struct", - type: "core::array::Span::", - }, - { - name: "Tuple", - type: "core::array::Span::", - }, - { - name: "Array", - type: "core::array::Span::", - }, - { - name: "ByteArray", - type: "()", - }, - { - name: "Enum", - type: "core::array::Span::", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "enum", - name: "core::bool", - variants: [ - { - name: "False", - type: "()", - }, - { - name: "True", - type: "()", - }, - ], - }, - { - type: "interface", - name: "dojo::world::iworld::IWorld", - items: [ - { - type: "function", - name: "resource", - inputs: [ - { - name: "selector", - type: "core::felt252", - }, - ], - outputs: [ - { - type: "dojo::world::resource::Resource", - }, - ], - state_mutability: "view", - }, - { - type: "function", - name: "uuid", - inputs: [], - outputs: [ - { - type: "core::integer::u32", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "metadata", - inputs: [ - { - name: "resource_selector", - type: "core::felt252", - }, - ], - outputs: [ - { - type: "dojo::model::metadata::ResourceMetadata", - }, - ], - state_mutability: "view", - }, - { - type: "function", - name: "set_metadata", - inputs: [ - { - name: "metadata", - type: "dojo::model::metadata::ResourceMetadata", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "register_namespace", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "register_event", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "register_model", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "register_contract", - inputs: [ - { - name: "salt", - type: "core::felt252", - }, - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [ - { - type: "core::starknet::contract_address::ContractAddress", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "register_library", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - { - name: "name", - type: "core::byte_array::ByteArray", - }, - { - name: "version", - type: "core::byte_array::ByteArray", - }, - ], - outputs: [ - { - type: "core::starknet::class_hash::ClassHash", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "init_contract", - inputs: [ - { - name: "selector", - type: "core::felt252", - }, - { - name: "init_calldata", - type: "core::array::Span::", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "upgrade_event", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "upgrade_model", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "upgrade_contract", - inputs: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [ - { - type: "core::starknet::class_hash::ClassHash", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "emit_event", - inputs: [ - { - name: "event_selector", - type: "core::felt252", - }, - { - name: "keys", - type: "core::array::Span::", - }, - { - name: "values", - type: "core::array::Span::", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "emit_events", - inputs: [ - { - name: "event_selector", - type: "core::felt252", - }, - { - name: "keys", - type: "core::array::Span::>", - }, - { - name: "values", - type: "core::array::Span::>", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "entity", - inputs: [ - { - name: "model_selector", - type: "core::felt252", - }, - { - name: "index", - type: "dojo::model::definition::ModelIndex", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - outputs: [ - { - type: "core::array::Span::", - }, - ], - state_mutability: "view", - }, - { - type: "function", - name: "entities", - inputs: [ - { - name: "model_selector", - type: "core::felt252", - }, - { - name: "indexes", - type: "core::array::Span::", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - outputs: [ - { - type: "core::array::Span::>", - }, - ], - state_mutability: "view", - }, - { - type: "function", - name: "set_entity", - inputs: [ - { - name: "model_selector", - type: "core::felt252", - }, - { - name: "index", - type: "dojo::model::definition::ModelIndex", - }, - { - name: "values", - type: "core::array::Span::", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "set_entities", - inputs: [ - { - name: "model_selector", - type: "core::felt252", - }, - { - name: "indexes", - type: "core::array::Span::", - }, - { - name: "values", - type: "core::array::Span::>", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "delete_entity", - inputs: [ - { - name: "model_selector", - type: "core::felt252", - }, - { - name: "index", - type: "dojo::model::definition::ModelIndex", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "delete_entities", - inputs: [ - { - name: "model_selector", - type: "core::felt252", - }, - { - name: "indexes", - type: "core::array::Span::", - }, - { - name: "layout", - type: "dojo::meta::layout::Layout", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "is_owner", - inputs: [ - { - name: "resource", - type: "core::felt252", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [ - { - type: "core::bool", - }, - ], - state_mutability: "view", - }, - { - type: "function", - name: "grant_owner", - inputs: [ - { - name: "resource", - type: "core::felt252", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "revoke_owner", - inputs: [ - { - name: "resource", - type: "core::felt252", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "is_writer", - inputs: [ - { - name: "resource", - type: "core::felt252", - }, - { - name: "contract", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [ - { - type: "core::bool", - }, - ], - state_mutability: "view", - }, - { - type: "function", - name: "grant_writer", - inputs: [ - { - name: "resource", - type: "core::felt252", - }, - { - name: "contract", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "revoke_writer", - inputs: [ - { - name: "resource", - type: "core::felt252", - }, - { - name: "contract", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "impl", - name: "UpgradeableWorld", - interface_name: "dojo::world::iworld::IUpgradeableWorld", - }, - { - type: "interface", - name: "dojo::world::iworld::IUpgradeableWorld", - items: [ - { - type: "function", - name: "upgrade", - inputs: [ - { - name: "new_class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "constructor", - name: "constructor", - inputs: [ - { - name: "world_class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::WorldSpawned", - kind: "struct", - members: [ - { - name: "creator", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::WorldUpgraded", - kind: "struct", - members: [ - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::NamespaceRegistered", - kind: "struct", - members: [ - { - name: "namespace", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "hash", - type: "core::felt252", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::ModelRegistered", - kind: "struct", - members: [ - { - name: "name", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "namespace", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::EventRegistered", - kind: "struct", - members: [ - { - name: "name", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "namespace", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::ContractRegistered", - kind: "struct", - members: [ - { - name: "name", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "namespace", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - { - name: "salt", - type: "core::felt252", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::ModelUpgraded", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - { - name: "prev_address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::EventUpgraded", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - { - name: "address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - { - name: "prev_address", - type: "core::starknet::contract_address::ContractAddress", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::ContractUpgraded", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::ContractInitialized", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "init_calldata", - type: "core::array::Span::", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::LibraryRegistered", - kind: "struct", - members: [ - { - name: "name", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "namespace", - type: "core::byte_array::ByteArray", - kind: "key", - }, - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::EventEmitted", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "system_address", - type: "core::starknet::contract_address::ContractAddress", - kind: "key", - }, - { - name: "keys", - type: "core::array::Span::", - kind: "data", - }, - { - name: "values", - type: "core::array::Span::", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::MetadataUpdate", - kind: "struct", - members: [ - { - name: "resource", - type: "core::felt252", - kind: "key", - }, - { - name: "uri", - type: "core::byte_array::ByteArray", - kind: "data", - }, - { - name: "hash", - type: "core::felt252", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::StoreSetRecord", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "entity_id", - type: "core::felt252", - kind: "key", - }, - { - name: "keys", - type: "core::array::Span::", - kind: "data", - }, - { - name: "values", - type: "core::array::Span::", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::StoreUpdateRecord", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "entity_id", - type: "core::felt252", - kind: "key", - }, - { - name: "values", - type: "core::array::Span::", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::StoreUpdateMember", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "entity_id", - type: "core::felt252", - kind: "key", - }, - { - name: "member_selector", - type: "core::felt252", - kind: "key", - }, - { - name: "values", - type: "core::array::Span::", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::StoreDelRecord", - kind: "struct", - members: [ - { - name: "selector", - type: "core::felt252", - kind: "key", - }, - { - name: "entity_id", - type: "core::felt252", - kind: "key", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::WriterUpdated", - kind: "struct", - members: [ - { - name: "resource", - type: "core::felt252", - kind: "key", - }, - { - name: "contract", - type: "core::starknet::contract_address::ContractAddress", - kind: "key", - }, - { - name: "value", - type: "core::bool", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::OwnerUpdated", - kind: "struct", - members: [ - { - name: "resource", - type: "core::felt252", - kind: "key", - }, - { - name: "contract", - type: "core::starknet::contract_address::ContractAddress", - kind: "key", - }, - { - name: "value", - type: "core::bool", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::world::world_contract::world::Event", - kind: "enum", - variants: [ - { - name: "WorldSpawned", - type: "dojo::world::world_contract::world::WorldSpawned", - kind: "nested", - }, - { - name: "WorldUpgraded", - type: "dojo::world::world_contract::world::WorldUpgraded", - kind: "nested", - }, - { - name: "NamespaceRegistered", - type: "dojo::world::world_contract::world::NamespaceRegistered", - kind: "nested", - }, - { - name: "ModelRegistered", - type: "dojo::world::world_contract::world::ModelRegistered", - kind: "nested", - }, - { - name: "EventRegistered", - type: "dojo::world::world_contract::world::EventRegistered", - kind: "nested", - }, - { - name: "ContractRegistered", - type: "dojo::world::world_contract::world::ContractRegistered", - kind: "nested", - }, - { - name: "ModelUpgraded", - type: "dojo::world::world_contract::world::ModelUpgraded", - kind: "nested", - }, - { - name: "EventUpgraded", - type: "dojo::world::world_contract::world::EventUpgraded", - kind: "nested", - }, - { - name: "ContractUpgraded", - type: "dojo::world::world_contract::world::ContractUpgraded", - kind: "nested", - }, - { - name: "ContractInitialized", - type: "dojo::world::world_contract::world::ContractInitialized", - kind: "nested", - }, - { - name: "LibraryRegistered", - type: "dojo::world::world_contract::world::LibraryRegistered", - kind: "nested", - }, - { - name: "EventEmitted", - type: "dojo::world::world_contract::world::EventEmitted", - kind: "nested", - }, - { - name: "MetadataUpdate", - type: "dojo::world::world_contract::world::MetadataUpdate", - kind: "nested", - }, - { - name: "StoreSetRecord", - type: "dojo::world::world_contract::world::StoreSetRecord", - kind: "nested", - }, - { - name: "StoreUpdateRecord", - type: "dojo::world::world_contract::world::StoreUpdateRecord", - kind: "nested", - }, - { - name: "StoreUpdateMember", - type: "dojo::world::world_contract::world::StoreUpdateMember", - kind: "nested", - }, - { - name: "StoreDelRecord", - type: "dojo::world::world_contract::world::StoreDelRecord", - kind: "nested", - }, - { - name: "WriterUpdated", - type: "dojo::world::world_contract::world::WriterUpdated", - kind: "nested", - }, - { - name: "OwnerUpdated", - type: "dojo::world::world_contract::world::OwnerUpdated", - kind: "nested", - }, - ], + return { + world: { + class_hash: + "0x7c9469d45a9cdbab775035afb48e1fa73fb35ab059fcb9dfb0a301aa973e783", + address: worldAddress, + seed: "pixelaw", + name: "pixelaw", + entrypoints: [ + "uuid", + "set_metadata", + "register_namespace", + "register_event", + "register_model", + "register_contract", + "register_library", + "init_contract", + "upgrade_event", + "upgrade_model", + "upgrade_contract", + "emit_event", + "emit_events", + "set_entity", + "set_entities", + "delete_entity", + "delete_entities", + "grant_owner", + "revoke_owner", + "grant_writer", + "revoke_writer", + "upgrade", + ], + abi: [ + { + type: "impl", + name: "World", + interface_name: "dojo::world::iworld::IWorld", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "enum", + name: "dojo::world::resource::Resource", + variants: [ + { + name: "Model", + type: "(core::starknet::contract_address::ContractAddress, core::felt252)", + }, + { + name: "Event", + type: "(core::starknet::contract_address::ContractAddress, core::felt252)", + }, + { + name: "Contract", + type: "(core::starknet::contract_address::ContractAddress, core::felt252)", + }, + { + name: "Namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "World", + type: "()", + }, + { + name: "Unregistered", + type: "()", + }, + { + name: "Library", + type: "(core::starknet::class_hash::ClassHash, core::felt252)", + }, + ], + }, + { + type: "struct", + name: "dojo::model::metadata::ResourceMetadata", + members: [ + { + name: "resource_id", + type: "core::felt252", + }, + { + name: "metadata_uri", + type: "core::byte_array::ByteArray", + }, + { + name: "metadata_hash", + type: "core::felt252", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::>", + members: [ + { + name: "snapshot", + type: "@core::array::Array::>", + }, + ], + }, + { + type: "enum", + name: "dojo::model::definition::ModelIndex", + variants: [ + { + name: "Keys", + type: "core::array::Span::", + }, + { + name: "Id", + type: "core::felt252", + }, + { + name: "MemberId", + type: "(core::felt252, core::felt252)", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "struct", + name: "dojo::meta::layout::FieldLayout", + members: [ + { + name: "selector", + type: "core::felt252", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "enum", + name: "dojo::meta::layout::Layout", + variants: [ + { + name: "Fixed", + type: "core::array::Span::", + }, + { + name: "Struct", + type: "core::array::Span::", + }, + { + name: "Tuple", + type: "core::array::Span::", + }, + { + name: "Array", + type: "core::array::Span::", + }, + { + name: "ByteArray", + type: "()", + }, + { + name: "Enum", + type: "core::array::Span::", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "enum", + name: "core::bool", + variants: [ + { + name: "False", + type: "()", + }, + { + name: "True", + type: "()", + }, + ], + }, + { + type: "interface", + name: "dojo::world::iworld::IWorld", + items: [ + { + type: "function", + name: "resource", + inputs: [ + { + name: "selector", + type: "core::felt252", }, - ], + ], + outputs: [ + { + type: "dojo::world::resource::Resource", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "uuid", + inputs: [], + outputs: [ + { + type: "core::integer::u32", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "metadata", + inputs: [ + { + name: "resource_selector", + type: "core::felt252", + }, + ], + outputs: [ + { + type: "dojo::model::metadata::ResourceMetadata", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "set_metadata", + inputs: [ + { + name: "metadata", + type: "dojo::model::metadata::ResourceMetadata", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "register_namespace", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "register_event", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "register_model", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "register_contract", + inputs: [ + { + name: "salt", + type: "core::felt252", + }, + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "register_library", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + { + name: "name", + type: "core::byte_array::ByteArray", + }, + { + name: "version", + type: "core::byte_array::ByteArray", + }, + ], + outputs: [ + { + type: "core::starknet::class_hash::ClassHash", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "init_contract", + inputs: [ + { + name: "selector", + type: "core::felt252", + }, + { + name: "init_calldata", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "upgrade_event", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "upgrade_model", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "upgrade_contract", + inputs: [ + { + name: "namespace", + type: "core::byte_array::ByteArray", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [ + { + type: "core::starknet::class_hash::ClassHash", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "emit_event", + inputs: [ + { + name: "event_selector", + type: "core::felt252", + }, + { + name: "keys", + type: "core::array::Span::", + }, + { + name: "values", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "emit_events", + inputs: [ + { + name: "event_selector", + type: "core::felt252", + }, + { + name: "keys", + type: "core::array::Span::>", + }, + { + name: "values", + type: "core::array::Span::>", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "entity", + inputs: [ + { + name: "model_selector", + type: "core::felt252", + }, + { + name: "index", + type: "dojo::model::definition::ModelIndex", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + outputs: [ + { + type: "core::array::Span::", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "entities", + inputs: [ + { + name: "model_selector", + type: "core::felt252", + }, + { + name: "indexes", + type: "core::array::Span::", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + outputs: [ + { + type: "core::array::Span::>", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "set_entity", + inputs: [ + { + name: "model_selector", + type: "core::felt252", + }, + { + name: "index", + type: "dojo::model::definition::ModelIndex", + }, + { + name: "values", + type: "core::array::Span::", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "set_entities", + inputs: [ + { + name: "model_selector", + type: "core::felt252", + }, + { + name: "indexes", + type: "core::array::Span::", + }, + { + name: "values", + type: "core::array::Span::>", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "delete_entity", + inputs: [ + { + name: "model_selector", + type: "core::felt252", + }, + { + name: "index", + type: "dojo::model::definition::ModelIndex", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "delete_entities", + inputs: [ + { + name: "model_selector", + type: "core::felt252", + }, + { + name: "indexes", + type: "core::array::Span::", + }, + { + name: "layout", + type: "dojo::meta::layout::Layout", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "is_owner", + inputs: [ + { + name: "resource", + type: "core::felt252", + }, + { + name: "address", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "grant_owner", + inputs: [ + { + name: "resource", + type: "core::felt252", + }, + { + name: "address", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "revoke_owner", + inputs: [ + { + name: "resource", + type: "core::felt252", + }, + { + name: "address", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "is_writer", + inputs: [ + { + name: "resource", + type: "core::felt252", + }, + { + name: "contract", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "grant_writer", + inputs: [ + { + name: "resource", + type: "core::felt252", + }, + { + name: "contract", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "revoke_writer", + inputs: [ + { + name: "resource", + type: "core::felt252", + }, + { + name: "contract", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "impl", + name: "UpgradeableWorld", + interface_name: "dojo::world::iworld::IUpgradeableWorld", + }, + { + type: "interface", + name: "dojo::world::iworld::IUpgradeableWorld", + items: [ + { + type: "function", + name: "upgrade", + inputs: [ + { + name: "new_class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], }, - contracts: [ - { - address: "0x5abe5e7947471f74a79d169091544f5caf38a24d1f66ed3c6ca3a0020ddcdde", - class_hash: "0x7668748f956d655d8f692126757b891e194be5abda7fb1742ad207339b3ab33", - abi: [ - { - type: "impl", - name: "actions__ContractImpl", - interface_name: "dojo::contract::interface::IContract", - }, - { - type: "interface", - name: "dojo::contract::interface::IContract", - items: [], - }, - { - type: "impl", - name: "actions__DeployedContractImpl", - interface_name: "dojo::meta::interface::IDeployedResource", - }, - { - type: "struct", - name: "core::byte_array::ByteArray", - members: [ - { - name: "data", - type: "core::array::Array::", - }, - { - name: "pending_word", - type: "core::felt252", - }, - { - name: "pending_word_len", - type: "core::integer::u32", - }, - ], - }, - { - type: "interface", - name: "dojo::meta::interface::IDeployedResource", - items: [ - { - type: "function", - name: "dojo_name", - inputs: [], - outputs: [ - { - type: "core::byte_array::ByteArray", - }, - ], - state_mutability: "view", - }, - ], - }, - { - type: "impl", - name: "ActionsImpl", - interface_name: "pixelaw::core::actions::IActions", - }, - { - type: "struct", - name: "pixelaw::core::models::pixel::Pixel", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - { - name: "app", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "color", - type: "core::integer::u32", - }, - { - name: "created_at", - type: "core::integer::u64", - }, - { - name: "updated_at", - type: "core::integer::u64", - }, - { - name: "timestamp", - type: "core::integer::u64", - }, - { - name: "owner", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "text", - type: "core::felt252", - }, - { - name: "action", - type: "core::felt252", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::integer::u32", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::felt252", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::integer::u64", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::pixel::PixelUpdate", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - { - name: "color", - type: "core::option::Option::", - }, - { - name: "owner", - type: "core::option::Option::", - }, - { - name: "app", - type: "core::option::Option::", - }, - { - name: "text", - type: "core::option::Option::", - }, - { - name: "timestamp", - type: "core::option::Option::", - }, - { - name: "action", - type: "core::option::Option::", - }, - ], - }, - { - type: "enum", - name: "core::bool", - variants: [ - { - name: "False", - type: "()", - }, - { - name: "True", - type: "()", - }, - ], - }, - { - type: "enum", - name: "pixelaw::core::models::pixel::PixelUpdateResult", - variants: [ - { - name: "Ok", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "NotAllowed", - type: "()", - }, - { - name: "Error", - type: "core::felt252", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::registry::App", - members: [ - { - name: "system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "name", - type: "core::felt252", - }, - { - name: "icon", - type: "core::felt252", - }, - { - name: "action", - type: "core::felt252", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::utils::Position", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::utils::Bounds", - members: [ - { - name: "x_min", - type: "core::integer::u16", - }, - { - name: "y_min", - type: "core::integer::u16", - }, - { - name: "x_max", - type: "core::integer::u16", - }, - { - name: "y_max", - type: "core::integer::u16", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::area::Area", - members: [ - { - name: "id", - type: "core::integer::u64", - }, - { - name: "app", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "owner", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "color", - type: "core::integer::u32", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "pixelaw::core::models::area::Area", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "interface", - name: "pixelaw::core::actions::IActions", - items: [ - { - type: "function", - name: "init", - inputs: [], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "can_update_pixel", - inputs: [ - { - name: "for_player", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "for_system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "pixel", - type: "pixelaw::core::models::pixel::Pixel", - }, - { - name: "pixel_update", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "area_id_hint", - type: "core::option::Option::", - }, - { - name: "allow_modify", - type: "core::bool", - }, - ], - outputs: [ - { - type: "pixelaw::core::models::pixel::PixelUpdateResult", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "update_pixel", - inputs: [ - { - name: "for_player", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "for_system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "pixel_update", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "area_id", - type: "core::option::Option::", - }, - { - name: "allow_modify", - type: "core::bool", - }, - ], - outputs: [ - { - type: "pixelaw::core::models::pixel::PixelUpdateResult", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "process_queue", - inputs: [ - { - name: "id", - type: "core::felt252", - }, - { - name: "timestamp", - type: "core::integer::u64", - }, - { - name: "called_system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "selector", - type: "core::felt252", - }, - { - name: "calldata", - type: "core::array::Span::", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "schedule_queue", - inputs: [ - { - name: "timestamp", - type: "core::integer::u64", - }, - { - name: "called_system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "selector", - type: "core::felt252", - }, - { - name: "calldata", - type: "core::array::Span::", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "new_app", - inputs: [ - { - name: "system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "name", - type: "core::felt252", - }, - { - name: "icon", - type: "core::felt252", - }, - ], - outputs: [ - { - type: "pixelaw::core::models::registry::App", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "alert_player", - inputs: [ - { - name: "position", - type: "pixelaw::core::utils::Position", - }, - { - name: "player", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "message", - type: "core::felt252", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "add_area", - inputs: [ - { - name: "bounds", - type: "pixelaw::core::utils::Bounds", - }, - { - name: "owner", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "color", - type: "core::integer::u32", - }, - { - name: "app", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [ - { - type: "pixelaw::core::models::area::Area", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "remove_area", - inputs: [ - { - name: "area_id", - type: "core::integer::u64", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "find_area_by_position", - inputs: [ - { - name: "position", - type: "pixelaw::core::utils::Position", - }, - ], - outputs: [ - { - type: "core::option::Option::", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "find_areas_inside_bounds", - inputs: [ - { - name: "bounds", - type: "pixelaw::core::utils::Bounds", - }, - ], - outputs: [ - { - type: "core::array::Span::", - }, - ], - state_mutability: "external", - }, - ], - }, - { - type: "function", - name: "dojo_init", - inputs: [], - outputs: [], - state_mutability: "view", - }, - { - type: "impl", - name: "WorldProviderImpl", - interface_name: "dojo::contract::components::world_provider::IWorldProvider", - }, - { - type: "struct", - name: "dojo::world::iworld::IWorldDispatcher", - members: [ - { - name: "contract_address", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - }, - { - type: "interface", - name: "dojo::contract::components::world_provider::IWorldProvider", - items: [ - { - type: "function", - name: "world_dispatcher", - inputs: [], - outputs: [ - { - type: "dojo::world::iworld::IWorldDispatcher", - }, - ], - state_mutability: "view", - }, - ], - }, - { - type: "impl", - name: "UpgradeableImpl", - interface_name: "dojo::contract::components::upgradeable::IUpgradeable", - }, - { - type: "interface", - name: "dojo::contract::components::upgradeable::IUpgradeable", - items: [ - { - type: "function", - name: "upgrade", - inputs: [ - { - name: "new_class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "constructor", - name: "constructor", - inputs: [], - }, - { - type: "event", - name: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", - kind: "struct", - members: [ - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", - kind: "enum", - variants: [ - { - name: "Upgraded", - type: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", - kind: "nested", - }, - ], - }, - { - type: "event", - name: "dojo::contract::components::world_provider::world_provider_cpt::Event", - kind: "enum", - variants: [], - }, - { - type: "event", - name: "pixelaw::core::actions::actions::Event", - kind: "enum", - variants: [ - { - name: "UpgradeableEvent", - type: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", - kind: "nested", - }, - { - name: "WorldProviderEvent", - type: "dojo::contract::components::world_provider::world_provider_cpt::Event", - kind: "nested", - }, - ], - }, - ], - init_calldata: [], - tag: "pixelaw-actions", - selector: "0x16928a49cfd8cf14e9f41e9d8f873890d1ab7d23b9a312d8a72f4031159876f", - systems: [ - "init", - "can_update_pixel", - "update_pixel", - "process_queue", - "schedule_queue", - "new_app", - "alert_player", - "add_area", - "remove_area", - "find_area_by_position", - "find_areas_inside_bounds", - "upgrade", - ], + { + type: "constructor", + name: "constructor", + inputs: [ + { + name: "world_class_hash", + type: "core::starknet::class_hash::ClassHash", }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::WorldSpawned", + kind: "struct", + members: [ { - address: "0x5e182fd8c6a2b35c0e9d465c8092d343ed9424c10b51a9c527c6fb250e42ed1", - class_hash: "0x9de2509144750b45f809c2e230648010b267b47732696f3178d6a98afe0015", - abi: [ - { - type: "impl", - name: "paint_actions__ContractImpl", - interface_name: "dojo::contract::interface::IContract", - }, - { - type: "interface", - name: "dojo::contract::interface::IContract", - items: [], - }, - { - type: "impl", - name: "paint_actions__DeployedContractImpl", - interface_name: "dojo::meta::interface::IDeployedResource", - }, - { - type: "struct", - name: "core::byte_array::ByteArray", - members: [ - { - name: "data", - type: "core::array::Array::", - }, - { - name: "pending_word", - type: "core::felt252", - }, - { - name: "pending_word_len", - type: "core::integer::u32", - }, - ], - }, - { - type: "interface", - name: "dojo::meta::interface::IDeployedResource", - items: [ - { - type: "function", - name: "dojo_name", - inputs: [], - outputs: [ - { - type: "core::byte_array::ByteArray", - }, - ], - state_mutability: "view", - }, - ], - }, - { - type: "impl", - name: "Actions", - interface_name: "pixelaw::apps::paint::app::IPaintActions", - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::integer::u32", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::felt252", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::integer::u64", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::pixel::PixelUpdate", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - { - name: "color", - type: "core::option::Option::", - }, - { - name: "owner", - type: "core::option::Option::", - }, - { - name: "app", - type: "core::option::Option::", - }, - { - name: "text", - type: "core::option::Option::", - }, - { - name: "timestamp", - type: "core::option::Option::", - }, - { - name: "action", - type: "core::option::Option::", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::registry::App", - members: [ - { - name: "system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "name", - type: "core::felt252", - }, - { - name: "icon", - type: "core::felt252", - }, - { - name: "action", - type: "core::felt252", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::utils::Position", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::utils::DefaultParameters", - members: [ - { - name: "player_override", - type: "core::option::Option::", - }, - { - name: "system_override", - type: "core::option::Option::", - }, - { - name: "area_hint", - type: "core::option::Option::", - }, - { - name: "position", - type: "pixelaw::core::utils::Position", - }, - { - name: "color", - type: "core::integer::u32", - }, - ], - }, - { - type: "struct", - name: "core::array::Span::", - members: [ - { - name: "snapshot", - type: "@core::array::Array::", - }, - ], - }, - { - type: "interface", - name: "pixelaw::apps::paint::app::IPaintActions", - items: [ - { - type: "function", - name: "init", - inputs: [], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "on_pre_update", - inputs: [ - { - name: "pixel_update", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "app_caller", - type: "pixelaw::core::models::registry::App", - }, - { - name: "player_caller", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [ - { - type: "core::option::Option::", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "on_post_update", - inputs: [ - { - name: "pixel_update", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "app_caller", - type: "pixelaw::core::models::registry::App", - }, - { - name: "player_caller", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "interact", - inputs: [ - { - name: "default_params", - type: "pixelaw::core::utils::DefaultParameters", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "put_color", - inputs: [ - { - name: "default_params", - type: "pixelaw::core::utils::DefaultParameters", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "fade", - inputs: [ - { - name: "default_params", - type: "pixelaw::core::utils::DefaultParameters", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "pixel_row", - inputs: [ - { - name: "default_params", - type: "pixelaw::core::utils::DefaultParameters", - }, - { - name: "image_data", - type: "core::array::Span::", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "function", - name: "dojo_init", - inputs: [], - outputs: [], - state_mutability: "view", - }, - { - type: "impl", - name: "WorldProviderImpl", - interface_name: "dojo::contract::components::world_provider::IWorldProvider", - }, - { - type: "struct", - name: "dojo::world::iworld::IWorldDispatcher", - members: [ - { - name: "contract_address", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - }, - { - type: "interface", - name: "dojo::contract::components::world_provider::IWorldProvider", - items: [ - { - type: "function", - name: "world_dispatcher", - inputs: [], - outputs: [ - { - type: "dojo::world::iworld::IWorldDispatcher", - }, - ], - state_mutability: "view", - }, - ], - }, - { - type: "impl", - name: "UpgradeableImpl", - interface_name: "dojo::contract::components::upgradeable::IUpgradeable", - }, - { - type: "interface", - name: "dojo::contract::components::upgradeable::IUpgradeable", - items: [ - { - type: "function", - name: "upgrade", - inputs: [ - { - name: "new_class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "constructor", - name: "constructor", - inputs: [], - }, - { - type: "event", - name: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", - kind: "struct", - members: [ - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", - kind: "enum", - variants: [ - { - name: "Upgraded", - type: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", - kind: "nested", - }, - ], - }, - { - type: "event", - name: "dojo::contract::components::world_provider::world_provider_cpt::Event", - kind: "enum", - variants: [], - }, - { - type: "event", - name: "pixelaw::apps::paint::app::paint_actions::Event", - kind: "enum", - variants: [ - { - name: "UpgradeableEvent", - type: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", - kind: "nested", - }, - { - name: "WorldProviderEvent", - type: "dojo::contract::components::world_provider::world_provider_cpt::Event", - kind: "nested", - }, - ], - }, - ], - init_calldata: [], - tag: "pixelaw-paint_actions", - selector: "0x2afb94fee3f58a7234658d0fd5366da8a9c48a4978cc6fff464344d2720123d", - systems: [ - "init", - "on_pre_update", - "on_post_update", - "interact", - "put_color", - "fade", - "pixel_row", - "upgrade", - ], + name: "creator", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", }, { - address: "0x1a73ac17c394f75e1bdb91943196839c4aa34e3f0fe2fdb50224d090a17158a", - class_hash: "0x5bde5cee7f53ff48b2a1a47a51ddb279c68a3777bfca0dda5b42d0257ffd2dc", - abi: [ - { - type: "impl", - name: "snake_actions__ContractImpl", - interface_name: "dojo::contract::interface::IContract", - }, - { - type: "interface", - name: "dojo::contract::interface::IContract", - items: [], - }, - { - type: "impl", - name: "snake_actions__DeployedContractImpl", - interface_name: "dojo::meta::interface::IDeployedResource", - }, - { - type: "struct", - name: "core::byte_array::ByteArray", - members: [ - { - name: "data", - type: "core::array::Array::", - }, - { - name: "pending_word", - type: "core::felt252", - }, - { - name: "pending_word_len", - type: "core::integer::u32", - }, - ], - }, - { - type: "interface", - name: "dojo::meta::interface::IDeployedResource", - items: [ - { - type: "function", - name: "dojo_name", - inputs: [], - outputs: [ - { - type: "core::byte_array::ByteArray", - }, - ], - state_mutability: "view", - }, - ], - }, - { - type: "impl", - name: "ActionsImpl", - interface_name: "pixelaw::apps::snake::app::ISnakeActions", - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::integer::u32", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::felt252", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "core::integer::u64", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::pixel::PixelUpdate", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - { - name: "color", - type: "core::option::Option::", - }, - { - name: "owner", - type: "core::option::Option::", - }, - { - name: "app", - type: "core::option::Option::", - }, - { - name: "text", - type: "core::option::Option::", - }, - { - name: "timestamp", - type: "core::option::Option::", - }, - { - name: "action", - type: "core::option::Option::", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::models::registry::App", - members: [ - { - name: "system", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "name", - type: "core::felt252", - }, - { - name: "icon", - type: "core::felt252", - }, - { - name: "action", - type: "core::felt252", - }, - ], - }, - { - type: "enum", - name: "core::option::Option::", - variants: [ - { - name: "Some", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "None", - type: "()", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::utils::Position", - members: [ - { - name: "x", - type: "core::integer::u16", - }, - { - name: "y", - type: "core::integer::u16", - }, - ], - }, - { - type: "struct", - name: "pixelaw::core::utils::DefaultParameters", - members: [ - { - name: "player_override", - type: "core::option::Option::", - }, - { - name: "system_override", - type: "core::option::Option::", - }, - { - name: "area_hint", - type: "core::option::Option::", - }, - { - name: "position", - type: "pixelaw::core::utils::Position", - }, - { - name: "color", - type: "core::integer::u32", - }, - ], - }, - { - type: "enum", - name: "pixelaw::core::utils::Direction", - variants: [ - { - name: "None", - type: "()", - }, - { - name: "Left", - type: "()", - }, - { - name: "Right", - type: "()", - }, - { - name: "Up", - type: "()", - }, - { - name: "Down", - type: "()", - }, - ], - }, - { - type: "interface", - name: "pixelaw::apps::snake::app::ISnakeActions", - items: [ - { - type: "function", - name: "on_pre_update", - inputs: [ - { - name: "pixel_update", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "app_caller", - type: "pixelaw::core::models::registry::App", - }, - { - name: "player_caller", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [ - { - type: "core::option::Option::", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "on_post_update", - inputs: [ - { - name: "pixel_update", - type: "pixelaw::core::models::pixel::PixelUpdate", - }, - { - name: "app_caller", - type: "pixelaw::core::models::registry::App", - }, - { - name: "player_caller", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "init", - inputs: [], - outputs: [], - state_mutability: "external", - }, - { - type: "function", - name: "interact", - inputs: [ - { - name: "default_params", - type: "pixelaw::core::utils::DefaultParameters", - }, - { - name: "direction", - type: "pixelaw::core::utils::Direction", - }, - ], - outputs: [ - { - type: "core::integer::u32", - }, - ], - state_mutability: "external", - }, - { - type: "function", - name: "move", - inputs: [ - { - name: "owner", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "function", - name: "dojo_init", - inputs: [], - outputs: [], - state_mutability: "view", - }, - { - type: "impl", - name: "WorldProviderImpl", - interface_name: "dojo::contract::components::world_provider::IWorldProvider", - }, - { - type: "struct", - name: "dojo::world::iworld::IWorldDispatcher", - members: [ - { - name: "contract_address", - type: "core::starknet::contract_address::ContractAddress", - }, - ], - }, - { - type: "interface", - name: "dojo::contract::components::world_provider::IWorldProvider", - items: [ - { - type: "function", - name: "world_dispatcher", - inputs: [], - outputs: [ - { - type: "dojo::world::iworld::IWorldDispatcher", - }, - ], - state_mutability: "view", - }, - ], - }, - { - type: "impl", - name: "UpgradeableImpl", - interface_name: "dojo::contract::components::upgradeable::IUpgradeable", - }, - { - type: "interface", - name: "dojo::contract::components::upgradeable::IUpgradeable", - items: [ - { - type: "function", - name: "upgrade", - inputs: [ - { - name: "new_class_hash", - type: "core::starknet::class_hash::ClassHash", - }, - ], - outputs: [], - state_mutability: "external", - }, - ], - }, - { - type: "constructor", - name: "constructor", - inputs: [], - }, - { - type: "event", - name: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", - kind: "struct", - members: [ - { - name: "class_hash", - type: "core::starknet::class_hash::ClassHash", - kind: "data", - }, - ], - }, - { - type: "event", - name: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", - kind: "enum", - variants: [ - { - name: "Upgraded", - type: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", - kind: "nested", - }, - ], - }, - { - type: "event", - name: "dojo::contract::components::world_provider::world_provider_cpt::Event", - kind: "enum", - variants: [], - }, - { - type: "event", - name: "pixelaw::apps::snake::app::snake_actions::Event", - kind: "enum", - variants: [ - { - name: "UpgradeableEvent", - type: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", - kind: "nested", - }, - { - name: "WorldProviderEvent", - type: "dojo::contract::components::world_provider::world_provider_cpt::Event", - kind: "nested", - }, - ], - }, - ], - init_calldata: [], - tag: "pixelaw-snake_actions", - selector: "0x4f96710669719291ad9660428d2dc9c921f129b75ddf0f70e8bf5837ea4157e", - systems: ["on_pre_update", "on_post_update", "init", "interact", "move", "upgrade"], + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", }, - ], - libraries: [], - models: [ + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::WorldUpgraded", + kind: "struct", + members: [ + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::NamespaceRegistered", + kind: "struct", + members: [ { - members: [], - class_hash: "0x69c806377ce49ee16eb6611f8a2918cab562ffd3e572fc57974956daf68ef77", - tag: "pixelaw-App", - selector: "0x3650456503601ce448928ac87c54e3a6e95e76d725a59c95c7201324c9b2b74", + name: "namespace", + type: "core::byte_array::ByteArray", + kind: "key", }, { - members: [], - class_hash: "0x5b2db85c691a4a3583660598d506a61050156f8ae224fe1c03de114a65500f9", - tag: "pixelaw-AppName", - selector: "0x3b816829f5d924b5acc1c44d28b6b61b4edd94e62444c536b2bdc85c0e70a2a", + name: "hash", + type: "core::felt252", + kind: "data", }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::ModelRegistered", + kind: "struct", + members: [ { - members: [], - class_hash: "0x5944128880d7bcd6e3efb666cf27b674f2494beeafcc0f1c26a87a84aa62eef", - tag: "pixelaw-AppUser", - selector: "0x4eda3c525958eca36164ba6f96e2b7591304838960197934ac8ae0a4b08b20f", + name: "name", + type: "core::byte_array::ByteArray", + kind: "key", }, { - members: [], - class_hash: "0x1c5ccb0f02d7239e6873da890f8b60609e9b09df0ffa48132198a984da05e", - tag: "pixelaw-Area", - selector: "0x41f22f1725b6e571bd66653e79fd700d80cc35c56f9dc5d663802e57478194", + name: "namespace", + type: "core::byte_array::ByteArray", + kind: "key", }, { - members: [], - class_hash: "0x6336b1cf57d40512193891849a69e7e128b697578061068430078c560983265", - tag: "pixelaw-CoreActionsAddress", - selector: "0x5379e1ce3a70cb70f1e96dae1b142164574f33d4e32cebdb965b5aec30222c5", + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", }, { - members: [], - class_hash: "0x2123dffc88b2c9fa4a96d047535f41ab43cd5d6eabbf3053666b7bfe013298", - tag: "pixelaw-Pixel", - selector: "0x7e607b2fbb4cfb3fb9d1258fa2ff3aa94f17b3820e42bf1e6a43e2de3f5772e", + name: "address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::EventRegistered", + kind: "struct", + members: [ { - members: [], - class_hash: "0x3b0515dfc0d09a190026b1880720cce4dba0beeea1c70ceb271cd8334b52161", - tag: "pixelaw-QueueItem", - selector: "0x549a17f23ab80595d9abd4d989a3d4bf0c1987ebc08ad48aecab0f1e8c311b4", + name: "name", + type: "core::byte_array::ByteArray", + kind: "key", }, { - members: [], - class_hash: "0x670fbb3ca4a5cc0e78e92951c7dd3013d453d820be28b50fb444c27d48d299", - tag: "pixelaw-RTree", - selector: "0x3baaf9fe25823e8928b6fc6400e28e98d4b7618ff56faf269a11f3400a1c105", + name: "namespace", + type: "core::byte_array::ByteArray", + kind: "key", }, { - members: [], - class_hash: "0x452c8f07ebd0d384499fe5a3810504a44f1feac9e447b26f1968c1a72259d43", - tag: "pixelaw-Snake", - selector: "0x62b876d4cd94e88d2c363c3515ce2c9e8af1badad47a8dc96966543970658c1", + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", }, { - members: [], - class_hash: "0x13552d32a060045bfbcde7531987c23e75b1956765b51693dd8dfed1098c2d8", - tag: "pixelaw-SnakeSegment", - selector: "0x302de0d87f8997cb65a4f7edb9a792706446961826bd4a16cbfb47fa09146ed", + name: "address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::ContractRegistered", + kind: "struct", + members: [ + { + name: "name", + type: "core::byte_array::ByteArray", + kind: "key", + }, + { + name: "namespace", + type: "core::byte_array::ByteArray", + kind: "key", + }, + { + name: "address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + { + name: "salt", + type: "core::felt252", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::ModelUpgraded", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + { + name: "address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", + }, + { + name: "prev_address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::EventUpgraded", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + { + name: "address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", + }, + { + name: "prev_address", + type: "core::starknet::contract_address::ContractAddress", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::ContractUpgraded", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::ContractInitialized", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "init_calldata", + type: "core::array::Span::", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::LibraryRegistered", + kind: "struct", + members: [ + { + name: "name", + type: "core::byte_array::ByteArray", + kind: "key", + }, + { + name: "namespace", + type: "core::byte_array::ByteArray", + kind: "key", + }, + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::EventEmitted", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "system_address", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "keys", + type: "core::array::Span::", + kind: "data", + }, + { + name: "values", + type: "core::array::Span::", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::MetadataUpdate", + kind: "struct", + members: [ + { + name: "resource", + type: "core::felt252", + kind: "key", + }, + { + name: "uri", + type: "core::byte_array::ByteArray", + kind: "data", + }, + { + name: "hash", + type: "core::felt252", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::StoreSetRecord", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "entity_id", + type: "core::felt252", + kind: "key", + }, + { + name: "keys", + type: "core::array::Span::", + kind: "data", + }, + { + name: "values", + type: "core::array::Span::", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::StoreUpdateRecord", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "entity_id", + type: "core::felt252", + kind: "key", + }, + { + name: "values", + type: "core::array::Span::", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::StoreUpdateMember", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "entity_id", + type: "core::felt252", + kind: "key", + }, + { + name: "member_selector", + type: "core::felt252", + kind: "key", + }, + { + name: "values", + type: "core::array::Span::", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::StoreDelRecord", + kind: "struct", + members: [ + { + name: "selector", + type: "core::felt252", + kind: "key", + }, + { + name: "entity_id", + type: "core::felt252", + kind: "key", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::WriterUpdated", + kind: "struct", + members: [ + { + name: "resource", + type: "core::felt252", + kind: "key", + }, + { + name: "contract", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "value", + type: "core::bool", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::OwnerUpdated", + kind: "struct", + members: [ + { + name: "resource", + type: "core::felt252", + kind: "key", + }, + { + name: "contract", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "value", + type: "core::bool", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::world::world_contract::world::Event", + kind: "enum", + variants: [ + { + name: "WorldSpawned", + type: "dojo::world::world_contract::world::WorldSpawned", + kind: "nested", + }, + { + name: "WorldUpgraded", + type: "dojo::world::world_contract::world::WorldUpgraded", + kind: "nested", + }, + { + name: "NamespaceRegistered", + type: "dojo::world::world_contract::world::NamespaceRegistered", + kind: "nested", }, - ], - events: [ { - members: [], - class_hash: "0x2d78ab0a0a6c395def4742913ada143407a416b43af4946ab417b2505de9d77", - tag: "pixelaw-Alert", - selector: "0x2a2938533e310a064aa2181f2cbb5914d80ac492be60f23fd358a49f47c26a2", + name: "ModelRegistered", + type: "dojo::world::world_contract::world::ModelRegistered", + kind: "nested", }, { - members: [], - class_hash: "0x43c6621153f4e3fc50bed2b5a3562c11f5a829cce5ee4ac267b700829475336", - tag: "pixelaw-Died", - selector: "0x7d747e04206de964d63ff53216dcde822fd170b7c1d55ef7fc92dce1d6dbad8", + name: "EventRegistered", + type: "dojo::world::world_contract::world::EventRegistered", + kind: "nested", }, { - members: [], - class_hash: "0x1972c0ed4bd91b733d74f50c00fa78be9088b62e3745dbb974ae401cc0d5e01", - tag: "pixelaw-Moved", - selector: "0x2b409f0cebc7c8b0091267d7ed51567a6c0aae7147a86c07b854a1cc448aa28", + name: "ContractRegistered", + type: "dojo::world::world_contract::world::ContractRegistered", + kind: "nested", }, { - members: [], - class_hash: "0x2983552d584a4de1de733aa50f04abf2f613243270693a390426418b54b5391", - tag: "pixelaw-QueueProcessed", - selector: "0x6998c9cd795c72fd0cab90978a79bcdbe098723ec9a67e725a17104677743eb", + name: "ModelUpgraded", + type: "dojo::world::world_contract::world::ModelUpgraded", + kind: "nested", }, { - members: [], - class_hash: "0x3ae8e6d38248c75be91fc47863a14e2999cb6416bf85ce6f97231ccd8f4a9ea", - tag: "pixelaw-QueueScheduled", - selector: "0x32e74bc9762cc0dcdb6c7b67b35ded5b22a785e7c924673d6163369e6c6f769", + name: "EventUpgraded", + type: "dojo::world::world_contract::world::EventUpgraded", + kind: "nested", }, + { + name: "ContractUpgraded", + type: "dojo::world::world_contract::world::ContractUpgraded", + kind: "nested", + }, + { + name: "ContractInitialized", + type: "dojo::world::world_contract::world::ContractInitialized", + kind: "nested", + }, + { + name: "LibraryRegistered", + type: "dojo::world::world_contract::world::LibraryRegistered", + kind: "nested", + }, + { + name: "EventEmitted", + type: "dojo::world::world_contract::world::EventEmitted", + kind: "nested", + }, + { + name: "MetadataUpdate", + type: "dojo::world::world_contract::world::MetadataUpdate", + kind: "nested", + }, + { + name: "StoreSetRecord", + type: "dojo::world::world_contract::world::StoreSetRecord", + kind: "nested", + }, + { + name: "StoreUpdateRecord", + type: "dojo::world::world_contract::world::StoreUpdateRecord", + kind: "nested", + }, + { + name: "StoreUpdateMember", + type: "dojo::world::world_contract::world::StoreUpdateMember", + kind: "nested", + }, + { + name: "StoreDelRecord", + type: "dojo::world::world_contract::world::StoreDelRecord", + kind: "nested", + }, + { + name: "WriterUpdated", + type: "dojo::world::world_contract::world::WriterUpdated", + kind: "nested", + }, + { + name: "OwnerUpdated", + type: "dojo::world::world_contract::world::OwnerUpdated", + kind: "nested", + }, + ], + }, + ], + }, + contracts: [ + { + address: + "0x5abe5e7947471f74a79d169091544f5caf38a24d1f66ed3c6ca3a0020ddcdde", + class_hash: + "0x7668748f956d655d8f692126757b891e194be5abda7fb1742ad207339b3ab33", + abi: [ + { + type: "impl", + name: "actions__ContractImpl", + interface_name: "dojo::contract::interface::IContract", + }, + { + type: "interface", + name: "dojo::contract::interface::IContract", + items: [], + }, + { + type: "impl", + name: "actions__DeployedContractImpl", + interface_name: "dojo::meta::interface::IDeployedResource", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "interface", + name: "dojo::meta::interface::IDeployedResource", + items: [ + { + type: "function", + name: "dojo_name", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "ActionsImpl", + interface_name: "pixelaw::core::actions::IActions", + }, + { + type: "struct", + name: "pixelaw::core::models::pixel::Pixel", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + { + name: "app", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "color", + type: "core::integer::u32", + }, + { + name: "created_at", + type: "core::integer::u64", + }, + { + name: "updated_at", + type: "core::integer::u64", + }, + { + name: "timestamp", + type: "core::integer::u64", + }, + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "text", + type: "core::felt252", + }, + { + name: "action", + type: "core::felt252", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u32", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::felt252", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u64", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::pixel::PixelUpdate", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + { + name: "color", + type: "core::option::Option::", + }, + { + name: "owner", + type: "core::option::Option::", + }, + { + name: "app", + type: "core::option::Option::", + }, + { + name: "text", + type: "core::option::Option::", + }, + { + name: "timestamp", + type: "core::option::Option::", + }, + { + name: "action", + type: "core::option::Option::", + }, + ], + }, + { + type: "enum", + name: "core::bool", + variants: [ + { + name: "False", + type: "()", + }, + { + name: "True", + type: "()", + }, + ], + }, + { + type: "enum", + name: "pixelaw::core::models::pixel::PixelUpdateResult", + variants: [ + { + name: "Ok", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "NotAllowed", + type: "()", + }, + { + name: "Error", + type: "core::felt252", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::registry::App", + members: [ + { + name: "system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "name", + type: "core::felt252", + }, + { + name: "icon", + type: "core::felt252", + }, + { + name: "action", + type: "core::felt252", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::utils::Position", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::utils::Bounds", + members: [ + { + name: "x_min", + type: "core::integer::u16", + }, + { + name: "y_min", + type: "core::integer::u16", + }, + { + name: "x_max", + type: "core::integer::u16", + }, + { + name: "y_max", + type: "core::integer::u16", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::area::Area", + members: [ + { + name: "id", + type: "core::integer::u64", + }, + { + name: "app", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "color", + type: "core::integer::u32", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "pixelaw::core::models::area::Area", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "interface", + name: "pixelaw::core::actions::IActions", + items: [ + { + type: "function", + name: "init", + inputs: [], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "can_update_pixel", + inputs: [ + { + name: "for_player", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "for_system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "pixel", + type: "pixelaw::core::models::pixel::Pixel", + }, + { + name: "pixel_update", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "area_id_hint", + type: "core::option::Option::", + }, + { + name: "allow_modify", + type: "core::bool", + }, + ], + outputs: [ + { + type: "pixelaw::core::models::pixel::PixelUpdateResult", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "update_pixel", + inputs: [ + { + name: "for_player", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "for_system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "pixel_update", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "area_id", + type: "core::option::Option::", + }, + { + name: "allow_modify", + type: "core::bool", + }, + ], + outputs: [ + { + type: "pixelaw::core::models::pixel::PixelUpdateResult", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "process_queue", + inputs: [ + { + name: "id", + type: "core::felt252", + }, + { + name: "timestamp", + type: "core::integer::u64", + }, + { + name: "called_system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "selector", + type: "core::felt252", + }, + { + name: "calldata", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "schedule_queue", + inputs: [ + { + name: "timestamp", + type: "core::integer::u64", + }, + { + name: "called_system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "selector", + type: "core::felt252", + }, + { + name: "calldata", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "new_app", + inputs: [ + { + name: "system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "name", + type: "core::felt252", + }, + { + name: "icon", + type: "core::felt252", + }, + ], + outputs: [ + { + type: "pixelaw::core::models::registry::App", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "alert_player", + inputs: [ + { + name: "position", + type: "pixelaw::core::utils::Position", + }, + { + name: "player", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "message", + type: "core::felt252", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "add_area", + inputs: [ + { + name: "bounds", + type: "pixelaw::core::utils::Bounds", + }, + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "color", + type: "core::integer::u32", + }, + { + name: "app", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "pixelaw::core::models::area::Area", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "remove_area", + inputs: [ + { + name: "area_id", + type: "core::integer::u64", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "find_area_by_position", + inputs: [ + { + name: "position", + type: "pixelaw::core::utils::Position", + }, + ], + outputs: [ + { + type: "core::option::Option::", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "find_areas_inside_bounds", + inputs: [ + { + name: "bounds", + type: "pixelaw::core::utils::Bounds", + }, + ], + outputs: [ + { + type: "core::array::Span::", + }, + ], + state_mutability: "external", + }, + ], + }, + { + type: "function", + name: "dojo_init", + inputs: [], + outputs: [], + state_mutability: "view", + }, + { + type: "impl", + name: "WorldProviderImpl", + interface_name: + "dojo::contract::components::world_provider::IWorldProvider", + }, + { + type: "struct", + name: "dojo::world::iworld::IWorldDispatcher", + members: [ + { + name: "contract_address", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + }, + { + type: "interface", + name: "dojo::contract::components::world_provider::IWorldProvider", + items: [ + { + type: "function", + name: "world_dispatcher", + inputs: [], + outputs: [ + { + type: "dojo::world::iworld::IWorldDispatcher", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "UpgradeableImpl", + interface_name: + "dojo::contract::components::upgradeable::IUpgradeable", + }, + { + type: "interface", + name: "dojo::contract::components::upgradeable::IUpgradeable", + items: [ + { + type: "function", + name: "upgrade", + inputs: [ + { + name: "new_class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "constructor", + name: "constructor", + inputs: [], + }, + { + type: "event", + name: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + kind: "struct", + members: [ + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + kind: "enum", + variants: [ + { + name: "Upgraded", + type: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "dojo::contract::components::world_provider::world_provider_cpt::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "pixelaw::core::actions::actions::Event", + kind: "enum", + variants: [ + { + name: "UpgradeableEvent", + type: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + kind: "nested", + }, + { + name: "WorldProviderEvent", + type: "dojo::contract::components::world_provider::world_provider_cpt::Event", + kind: "nested", + }, + ], + }, + ], + init_calldata: [], + tag: "pixelaw-actions", + selector: + "0x16928a49cfd8cf14e9f41e9d8f873890d1ab7d23b9a312d8a72f4031159876f", + systems: [ + "init", + "can_update_pixel", + "update_pixel", + "process_queue", + "schedule_queue", + "new_app", + "alert_player", + "add_area", + "remove_area", + "find_area_by_position", + "find_areas_inside_bounds", + "upgrade", + ], + }, + { + address: + "0x5e182fd8c6a2b35c0e9d465c8092d343ed9424c10b51a9c527c6fb250e42ed1", + class_hash: + "0x9de2509144750b45f809c2e230648010b267b47732696f3178d6a98afe0015", + abi: [ + { + type: "impl", + name: "paint_actions__ContractImpl", + interface_name: "dojo::contract::interface::IContract", + }, + { + type: "interface", + name: "dojo::contract::interface::IContract", + items: [], + }, + { + type: "impl", + name: "paint_actions__DeployedContractImpl", + interface_name: "dojo::meta::interface::IDeployedResource", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "interface", + name: "dojo::meta::interface::IDeployedResource", + items: [ + { + type: "function", + name: "dojo_name", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "Actions", + interface_name: "pixelaw::apps::paint::app::IPaintActions", + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u32", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::felt252", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u64", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::pixel::PixelUpdate", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + { + name: "color", + type: "core::option::Option::", + }, + { + name: "owner", + type: "core::option::Option::", + }, + { + name: "app", + type: "core::option::Option::", + }, + { + name: "text", + type: "core::option::Option::", + }, + { + name: "timestamp", + type: "core::option::Option::", + }, + { + name: "action", + type: "core::option::Option::", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::registry::App", + members: [ + { + name: "system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "name", + type: "core::felt252", + }, + { + name: "icon", + type: "core::felt252", + }, + { + name: "action", + type: "core::felt252", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::utils::Position", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::utils::DefaultParameters", + members: [ + { + name: "player_override", + type: "core::option::Option::", + }, + { + name: "system_override", + type: "core::option::Option::", + }, + { + name: "area_hint", + type: "core::option::Option::", + }, + { + name: "position", + type: "pixelaw::core::utils::Position", + }, + { + name: "color", + type: "core::integer::u32", + }, + ], + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "interface", + name: "pixelaw::apps::paint::app::IPaintActions", + items: [ + { + type: "function", + name: "init", + inputs: [], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "on_pre_update", + inputs: [ + { + name: "pixel_update", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "app_caller", + type: "pixelaw::core::models::registry::App", + }, + { + name: "player_caller", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::option::Option::", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "on_post_update", + inputs: [ + { + name: "pixel_update", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "app_caller", + type: "pixelaw::core::models::registry::App", + }, + { + name: "player_caller", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "interact", + inputs: [ + { + name: "default_params", + type: "pixelaw::core::utils::DefaultParameters", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "put_color", + inputs: [ + { + name: "default_params", + type: "pixelaw::core::utils::DefaultParameters", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "fade", + inputs: [ + { + name: "default_params", + type: "pixelaw::core::utils::DefaultParameters", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "pixel_row", + inputs: [ + { + name: "default_params", + type: "pixelaw::core::utils::DefaultParameters", + }, + { + name: "image_data", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "function", + name: "dojo_init", + inputs: [], + outputs: [], + state_mutability: "view", + }, + { + type: "impl", + name: "WorldProviderImpl", + interface_name: + "dojo::contract::components::world_provider::IWorldProvider", + }, + { + type: "struct", + name: "dojo::world::iworld::IWorldDispatcher", + members: [ + { + name: "contract_address", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + }, + { + type: "interface", + name: "dojo::contract::components::world_provider::IWorldProvider", + items: [ + { + type: "function", + name: "world_dispatcher", + inputs: [], + outputs: [ + { + type: "dojo::world::iworld::IWorldDispatcher", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "UpgradeableImpl", + interface_name: + "dojo::contract::components::upgradeable::IUpgradeable", + }, + { + type: "interface", + name: "dojo::contract::components::upgradeable::IUpgradeable", + items: [ + { + type: "function", + name: "upgrade", + inputs: [ + { + name: "new_class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "constructor", + name: "constructor", + inputs: [], + }, + { + type: "event", + name: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + kind: "struct", + members: [ + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + kind: "enum", + variants: [ + { + name: "Upgraded", + type: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "dojo::contract::components::world_provider::world_provider_cpt::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "pixelaw::apps::paint::app::paint_actions::Event", + kind: "enum", + variants: [ + { + name: "UpgradeableEvent", + type: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + kind: "nested", + }, + { + name: "WorldProviderEvent", + type: "dojo::contract::components::world_provider::world_provider_cpt::Event", + kind: "nested", + }, + ], + }, + ], + init_calldata: [], + tag: "pixelaw-paint_actions", + selector: + "0x2afb94fee3f58a7234658d0fd5366da8a9c48a4978cc6fff464344d2720123d", + systems: [ + "init", + "on_pre_update", + "on_post_update", + "interact", + "put_color", + "fade", + "pixel_row", + "upgrade", + ], + }, + { + address: + "0x1a73ac17c394f75e1bdb91943196839c4aa34e3f0fe2fdb50224d090a17158a", + class_hash: + "0x5bde5cee7f53ff48b2a1a47a51ddb279c68a3777bfca0dda5b42d0257ffd2dc", + abi: [ + { + type: "impl", + name: "snake_actions__ContractImpl", + interface_name: "dojo::contract::interface::IContract", + }, + { + type: "interface", + name: "dojo::contract::interface::IContract", + items: [], + }, + { + type: "impl", + name: "snake_actions__DeployedContractImpl", + interface_name: "dojo::meta::interface::IDeployedResource", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "interface", + name: "dojo::meta::interface::IDeployedResource", + items: [ + { + type: "function", + name: "dojo_name", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "ActionsImpl", + interface_name: "pixelaw::apps::snake::app::ISnakeActions", + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u32", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::felt252", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "core::integer::u64", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::pixel::PixelUpdate", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + { + name: "color", + type: "core::option::Option::", + }, + { + name: "owner", + type: "core::option::Option::", + }, + { + name: "app", + type: "core::option::Option::", + }, + { + name: "text", + type: "core::option::Option::", + }, + { + name: "timestamp", + type: "core::option::Option::", + }, + { + name: "action", + type: "core::option::Option::", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::models::registry::App", + members: [ + { + name: "system", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "name", + type: "core::felt252", + }, + { + name: "icon", + type: "core::felt252", + }, + { + name: "action", + type: "core::felt252", + }, + ], + }, + { + type: "enum", + name: "core::option::Option::", + variants: [ + { + name: "Some", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "None", + type: "()", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::utils::Position", + members: [ + { + name: "x", + type: "core::integer::u16", + }, + { + name: "y", + type: "core::integer::u16", + }, + ], + }, + { + type: "struct", + name: "pixelaw::core::utils::DefaultParameters", + members: [ + { + name: "player_override", + type: "core::option::Option::", + }, + { + name: "system_override", + type: "core::option::Option::", + }, + { + name: "area_hint", + type: "core::option::Option::", + }, + { + name: "position", + type: "pixelaw::core::utils::Position", + }, + { + name: "color", + type: "core::integer::u32", + }, + ], + }, + { + type: "enum", + name: "pixelaw::core::utils::Direction", + variants: [ + { + name: "None", + type: "()", + }, + { + name: "Left", + type: "()", + }, + { + name: "Right", + type: "()", + }, + { + name: "Up", + type: "()", + }, + { + name: "Down", + type: "()", + }, + ], + }, + { + type: "interface", + name: "pixelaw::apps::snake::app::ISnakeActions", + items: [ + { + type: "function", + name: "on_pre_update", + inputs: [ + { + name: "pixel_update", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "app_caller", + type: "pixelaw::core::models::registry::App", + }, + { + name: "player_caller", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::option::Option::", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "on_post_update", + inputs: [ + { + name: "pixel_update", + type: "pixelaw::core::models::pixel::PixelUpdate", + }, + { + name: "app_caller", + type: "pixelaw::core::models::registry::App", + }, + { + name: "player_caller", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "init", + inputs: [], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "interact", + inputs: [ + { + name: "default_params", + type: "pixelaw::core::utils::DefaultParameters", + }, + { + name: "direction", + type: "pixelaw::core::utils::Direction", + }, + ], + outputs: [ + { + type: "core::integer::u32", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "move", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "function", + name: "dojo_init", + inputs: [], + outputs: [], + state_mutability: "view", + }, + { + type: "impl", + name: "WorldProviderImpl", + interface_name: + "dojo::contract::components::world_provider::IWorldProvider", + }, + { + type: "struct", + name: "dojo::world::iworld::IWorldDispatcher", + members: [ + { + name: "contract_address", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + }, + { + type: "interface", + name: "dojo::contract::components::world_provider::IWorldProvider", + items: [ + { + type: "function", + name: "world_dispatcher", + inputs: [], + outputs: [ + { + type: "dojo::world::iworld::IWorldDispatcher", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "UpgradeableImpl", + interface_name: + "dojo::contract::components::upgradeable::IUpgradeable", + }, + { + type: "interface", + name: "dojo::contract::components::upgradeable::IUpgradeable", + items: [ + { + type: "function", + name: "upgrade", + inputs: [ + { + name: "new_class_hash", + type: "core::starknet::class_hash::ClassHash", + }, + ], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "constructor", + name: "constructor", + inputs: [], + }, + { + type: "event", + name: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + kind: "struct", + members: [ + { + name: "class_hash", + type: "core::starknet::class_hash::ClassHash", + kind: "data", + }, + ], + }, + { + type: "event", + name: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + kind: "enum", + variants: [ + { + name: "Upgraded", + type: "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "dojo::contract::components::world_provider::world_provider_cpt::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "pixelaw::apps::snake::app::snake_actions::Event", + kind: "enum", + variants: [ + { + name: "UpgradeableEvent", + type: "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + kind: "nested", + }, + { + name: "WorldProviderEvent", + type: "dojo::contract::components::world_provider::world_provider_cpt::Event", + kind: "nested", + }, + ], + }, + ], + init_calldata: [], + tag: "pixelaw-snake_actions", + selector: + "0x4f96710669719291ad9660428d2dc9c921f129b75ddf0f70e8bf5837ea4157e", + systems: [ + "on_pre_update", + "on_post_update", + "init", + "interact", + "move", + "upgrade", ], - external_contracts: [], - } + }, + ], + libraries: [], + models: [ + { + members: [], + class_hash: + "0x69c806377ce49ee16eb6611f8a2918cab562ffd3e572fc57974956daf68ef77", + tag: "pixelaw-App", + selector: + "0x3650456503601ce448928ac87c54e3a6e95e76d725a59c95c7201324c9b2b74", + }, + { + members: [], + class_hash: + "0x5b2db85c691a4a3583660598d506a61050156f8ae224fe1c03de114a65500f9", + tag: "pixelaw-AppName", + selector: + "0x3b816829f5d924b5acc1c44d28b6b61b4edd94e62444c536b2bdc85c0e70a2a", + }, + { + members: [], + class_hash: + "0x5944128880d7bcd6e3efb666cf27b674f2494beeafcc0f1c26a87a84aa62eef", + tag: "pixelaw-AppUser", + selector: + "0x4eda3c525958eca36164ba6f96e2b7591304838960197934ac8ae0a4b08b20f", + }, + { + members: [], + class_hash: + "0x1c5ccb0f02d7239e6873da890f8b60609e9b09df0ffa48132198a984da05e", + tag: "pixelaw-Area", + selector: + "0x41f22f1725b6e571bd66653e79fd700d80cc35c56f9dc5d663802e57478194", + }, + { + members: [], + class_hash: + "0x6336b1cf57d40512193891849a69e7e128b697578061068430078c560983265", + tag: "pixelaw-CoreActionsAddress", + selector: + "0x5379e1ce3a70cb70f1e96dae1b142164574f33d4e32cebdb965b5aec30222c5", + }, + { + members: [], + class_hash: + "0x2123dffc88b2c9fa4a96d047535f41ab43cd5d6eabbf3053666b7bfe013298", + tag: "pixelaw-Pixel", + selector: + "0x7e607b2fbb4cfb3fb9d1258fa2ff3aa94f17b3820e42bf1e6a43e2de3f5772e", + }, + { + members: [], + class_hash: + "0x3b0515dfc0d09a190026b1880720cce4dba0beeea1c70ceb271cd8334b52161", + tag: "pixelaw-QueueItem", + selector: + "0x549a17f23ab80595d9abd4d989a3d4bf0c1987ebc08ad48aecab0f1e8c311b4", + }, + { + members: [], + class_hash: + "0x670fbb3ca4a5cc0e78e92951c7dd3013d453d820be28b50fb444c27d48d299", + tag: "pixelaw-RTree", + selector: + "0x3baaf9fe25823e8928b6fc6400e28e98d4b7618ff56faf269a11f3400a1c105", + }, + { + members: [], + class_hash: + "0x452c8f07ebd0d384499fe5a3810504a44f1feac9e447b26f1968c1a72259d43", + tag: "pixelaw-Snake", + selector: + "0x62b876d4cd94e88d2c363c3515ce2c9e8af1badad47a8dc96966543970658c1", + }, + { + members: [], + class_hash: + "0x13552d32a060045bfbcde7531987c23e75b1956765b51693dd8dfed1098c2d8", + tag: "pixelaw-SnakeSegment", + selector: + "0x302de0d87f8997cb65a4f7edb9a792706446961826bd4a16cbfb47fa09146ed", + }, + ], + events: [ + { + members: [], + class_hash: + "0x2d78ab0a0a6c395def4742913ada143407a416b43af4946ab417b2505de9d77", + tag: "pixelaw-Alert", + selector: + "0x2a2938533e310a064aa2181f2cbb5914d80ac492be60f23fd358a49f47c26a2", + }, + { + members: [], + class_hash: + "0x43c6621153f4e3fc50bed2b5a3562c11f5a829cce5ee4ac267b700829475336", + tag: "pixelaw-Died", + selector: + "0x7d747e04206de964d63ff53216dcde822fd170b7c1d55ef7fc92dce1d6dbad8", + }, + { + members: [], + class_hash: + "0x1972c0ed4bd91b733d74f50c00fa78be9088b62e3745dbb974ae401cc0d5e01", + tag: "pixelaw-Moved", + selector: + "0x2b409f0cebc7c8b0091267d7ed51567a6c0aae7147a86c07b854a1cc448aa28", + }, + { + members: [], + class_hash: + "0x2983552d584a4de1de733aa50f04abf2f613243270693a390426418b54b5391", + tag: "pixelaw-QueueProcessed", + selector: + "0x6998c9cd795c72fd0cab90978a79bcdbe098723ec9a67e725a17104677743eb", + }, + { + members: [], + class_hash: + "0x3ae8e6d38248c75be91fc47863a14e2999cb6416bf85ce6f97231ccd8f4a9ea", + tag: "pixelaw-QueueScheduled", + selector: + "0x32e74bc9762cc0dcdb6c7b67b35ded5b22a785e7c924673d6163369e6c6f769", + }, + ], + external_contracts: [], + }; } diff --git a/packages/core-dojo/src/utils/parseEvents.ts b/packages/core-dojo/src/utils/parseEvents.ts index a3140c0..c85aa59 100644 --- a/packages/core-dojo/src/utils/parseEvents.ts +++ b/packages/core-dojo/src/utils/parseEvents.ts @@ -1,42 +1,47 @@ -import type { Pixel } from "@pixelaw/core" -import { type BigNumberish, type LegacyContractClass, type RpcProvider, shortString } from "starknet" +import type { Pixel } from "@pixelaw/core"; +import { + type BigNumberish, + type LegacyContractClass, + type RpcProvider, + shortString, +} from "starknet"; -import { convertFullHexString } from "./utils.ts" +import { convertFullHexString } from "./utils.ts"; export function parseEventsFromSimulation(modelId, json): Pixel[] { - const invocation = json[0].transaction_trace.execute_invocation.calls + const invocation = json[0].transaction_trace.execute_invocation.calls; - const extractEvents = (calls) => { - return calls.reduce((events, call) => { - if (call.events && call.events.length > 0) { - const pixelEvents = call.events.filter((e) => { - return e.keys.includes(modelId) - }) - events = events.concat(pixelEvents) - } - if (call.calls && call.calls.length > 0) { - events = events.concat(extractEvents(call.calls)) - } - return events - }, []) - } + const extractEvents = (calls) => { + return calls.reduce((events, call) => { + if (call.events && call.events.length > 0) { + const pixelEvents = call.events.filter((e) => { + return e.keys.includes(modelId); + }); + events = events.concat(pixelEvents); + } + if (call.calls && call.calls.length > 0) { + events = events.concat(extractEvents(call.calls)); + } + return events; + }, []); + }; - const events = extractEvents(invocation) + const events = extractEvents(invocation); - const pixels = events.map((e) => { - const ret = { - x: Number.parseInt(e.data[1], 16), - y: Number.parseInt(e.data[2]), - app: e.data[4], - color: e.data[5], - created_at: Number.parseInt(e.data[6]), - updated_at: Number.parseInt(e.data[7]), - timestamp: Number.parseInt(e.data[8]), - owner: e.data[9], - text: convertFullHexString(e.data[10]), - action: shortString.decodeShortString(e.data[11]), - } as Pixel - return ret - }) + const pixels = events.map((e) => { + const ret = { + x: Number.parseInt(e.data[1], 16), + y: Number.parseInt(e.data[2]), + app: e.data[4], + color: e.data[5], + created_at: Number.parseInt(e.data[6]), + updated_at: Number.parseInt(e.data[7]), + timestamp: Number.parseInt(e.data[8]), + owner: e.data[9], + text: convertFullHexString(e.data[10]), + action: shortString.decodeShortString(e.data[11]), + } as Pixel; + return ret; + }); - return pixels + return pixels; } diff --git a/packages/core-dojo/src/utils/querybuilder.ts b/packages/core-dojo/src/utils/querybuilder.ts index ef3a104..1bafdd3 100644 --- a/packages/core-dojo/src/utils/querybuilder.ts +++ b/packages/core-dojo/src/utils/querybuilder.ts @@ -1,43 +1,53 @@ -import type { SchemaType } from "../generated/models.gen.ts" -import { ToriiQueryBuilder } from "@dojoengine/sdk" -import { type Bounds, MAX_DIMENSION, QUERY_BUFFER } from "@pixelaw/core" +import type { SchemaType } from "../generated/models.gen.ts"; +import { ToriiQueryBuilder } from "@dojoengine/sdk"; +import { type Bounds, MAX_DIMENSION, QUERY_BUFFER } from "@pixelaw/core"; export function getQueryBounds(viewBounds: Bounds): Bounds { - let [[left, top], [right, bottom]] = viewBounds + let [[left, top], [right, bottom]] = viewBounds; - left = Math.max(left - (left % QUERY_BUFFER), 0) - right = Math.min(right + (QUERY_BUFFER - (right % QUERY_BUFFER)), MAX_DIMENSION) - top = Math.max(top - (top % QUERY_BUFFER), 0) - bottom = Math.min(bottom + (QUERY_BUFFER - (bottom % QUERY_BUFFER)), MAX_DIMENSION) + left = Math.max(left - (left % QUERY_BUFFER), 0); + right = Math.min( + right + (QUERY_BUFFER - (right % QUERY_BUFFER)), + MAX_DIMENSION, + ); + top = Math.max(top - (top % QUERY_BUFFER), 0); + bottom = Math.min( + bottom + (QUERY_BUFFER - (bottom % QUERY_BUFFER)), + MAX_DIMENSION, + ); - return [ - [left, top], - [right, bottom], - ] + return [ + [left, top], + [right, bottom], + ]; } export function buildSubscriptionQuery(): ToriiQueryBuilder { - const builder = new ToriiQueryBuilder() - const timestamp = Date.now() - // TODO tweak the query, it works with withLimit(10) and withOffset(10) but removing them - // leads to a grpc error. - const query = builder.withLimit(10).withOffset(0).addEntityModel("pixelaw-Pixel").updatedAfter(timestamp) - return query + const builder = new ToriiQueryBuilder(); + const timestamp = Date.now(); + // TODO tweak the query, it works with withLimit(10) and withOffset(10) but removing them + // leads to a grpc error. + const query = builder + .withLimit(10) + .withOffset(0) + .addEntityModel("pixelaw-Pixel") + .updatedAfter(timestamp); + return query; } export const SUBSCRIPTION_QUERY = { - pixelaw: { - Pixel: { - $: { - where: { - And: [ - { x: { $gte: 0 } }, - { y: { $gte: 0 } }, - { x: { $lte: MAX_DIMENSION } }, - { y: { $lte: MAX_DIMENSION } }, - ], - }, - }, + pixelaw: { + Pixel: { + $: { + where: { + And: [ + { x: { $gte: 0 } }, + { y: { $gte: 0 } }, + { x: { $lte: MAX_DIMENSION } }, + { y: { $lte: MAX_DIMENSION } }, + ], }, + }, }, -} + }, +}; diff --git a/packages/core-dojo/src/utils/utils.starknet.ts b/packages/core-dojo/src/utils/utils.starknet.ts index c3380ff..cb7ed8f 100644 --- a/packages/core-dojo/src/utils/utils.starknet.ts +++ b/packages/core-dojo/src/utils/utils.starknet.ts @@ -1,61 +1,67 @@ -import type { App } from "@pixelaw/core" // BEWARE: it seems that using this in a service/webworker crashes with error: -import { type BigNumberish, type RpcProvider, shortString } from "starknet" -import type { SimpleContract } from "../types.ts" +import type { App } from "@pixelaw/core"; // BEWARE: it seems that using this in a service/webworker crashes with error: +import { type BigNumberish, type RpcProvider, shortString } from "starknet"; +import type { SimpleContract } from "../types.ts"; // BEWARE: it seems that using this in a service/webworker crashes with a "global not found" ponycode error -export async function getClass(provider: RpcProvider, system: BigNumberish): Promise { - const ch = await provider.getClassHashAt(system) - return await provider.getClass(ch) +export async function getClass( + provider: RpcProvider, + system: BigNumberish, +): Promise { + const ch = await provider.getClassHashAt(system); + return await provider.getClass(ch); } -export async function getAbi(provider: RpcProvider, app: App): Promise { - let name = felt252ToString(app.name).toLowerCase() +export async function getAbi( + provider: RpcProvider, + app: App, +): Promise { + let name = felt252ToString(app.name).toLowerCase(); - const cl = await getClass(provider, app.system) + const cl = await getClass(provider, app.system); - const tag = `pixelaw-${name}_actions` - name = `pixelaw::apps::${name}::app::${name}_actions` + const tag = `pixelaw-${name}_actions`; + name = `pixelaw::apps::${name}::app::${name}_actions`; - return { - kind: "DojoContract", - address: app.system, - abi: cl.abi, - name, - tag, - } + return { + kind: "DojoContract", + address: app.system, + abi: cl.abi, + name, + tag, + }; } export const felt252ToString = (felt252Input: string | number | bigint) => { - let result = felt252Input + let result = felt252Input; - if (typeof result === "bigint" || typeof result === "object") { - result = `0x${result.toString(16)}` - } - if (result === "0x0" || result === "0") return "" - if (typeof result === "string") { - try { - // biome-ignore lint/suspicious/noControlCharactersInRegex: Somehow null characters are in the string - return shortString.decodeShortString(result).replace(/^\u0000+/, "") - } catch (e) { - return result - } + if (typeof result === "bigint" || typeof result === "object") { + result = `0x${result.toString(16)}`; + } + if (result === "0x0" || result === "0") return ""; + if (typeof result === "string") { + try { + // biome-ignore lint/suspicious/noControlCharactersInRegex: Somehow null characters are in the string + return shortString.decodeShortString(result).replace(/^\u0000+/, ""); + } catch (e) { + return result; } - return result.toString() -} + } + return result.toString(); +}; export const formatAddress = (address: string) => { - if (address.length > 30) { - return `${address.substr(0, 6)}...${address.substr(address.length - 4, address.length)}` - } + if (address.length > 30) { + return `${address.substr(0, 6)}...${address.substr(address.length - 4, address.length)}`; + } - return address -} + return address; +}; export const felt252ToUnicode = (felt252: string | number) => { - const string = felt252ToString(felt252) - if (string.includes("U+")) { - const text = string.replace("U+", "") - const codePoint = Number.parseInt(text, 16) - return String.fromCodePoint(codePoint) - } - return string -} + const string = felt252ToString(felt252); + if (string.includes("U+")) { + const text = string.replace("U+", ""); + const codePoint = Number.parseInt(text, 16); + return String.fromCodePoint(codePoint); + } + return string; +}; diff --git a/packages/core-dojo/src/utils/utils.ts b/packages/core-dojo/src/utils/utils.ts index 28ec58c..e168ed2 100644 --- a/packages/core-dojo/src/utils/utils.ts +++ b/packages/core-dojo/src/utils/utils.ts @@ -1,124 +1,128 @@ // TODO technically this is only 248bits randomness.. export function generateRandomFelt252(): bigint { - let randomBytes: Uint8Array + let randomBytes: Uint8Array; - if (typeof window !== "undefined" && window.crypto) { - // Browser environment - randomBytes = new Uint8Array(31) - window.crypto.getRandomValues(randomBytes) - } else { - // Node.js environment - const crypto = require("node:crypto") - randomBytes = crypto.randomBytes(31) - } + if (typeof window !== "undefined" && window.crypto) { + // Browser environment + randomBytes = new Uint8Array(31); + window.crypto.getRandomValues(randomBytes); + } else { + // Node.js environment + const crypto = require("node:crypto"); + randomBytes = crypto.randomBytes(31); + } - return BigInt( - `0x${Array.from(randomBytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join("")}`, - ) + return BigInt( + `0x${Array.from(randomBytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join("")}`, + ); } export function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)); } // takes a "0x000000000000000000" string and turns it into utf8 emoji export function convertFullHexString(str) { - // debugger - const result = str.replace("0x", "").replace(/^0+/, "") - if (!result.length) return "" - const r = parseText(result) - return r + // debugger + const result = str.replace("0x", "").replace(/^0+/, ""); + if (!result.length) return ""; + const r = parseText(result); + return r; } export function parseText(str: string): string { - if (!str.length) return str + if (!str.length) return str; - // The string now contains hex representing utf8 codepoints - // Convert hex string to a Uint8Array - const bytes = new Uint8Array(str.match(/.{1,2}/g)!.map((byte) => Number.parseInt(byte, 16))) + // The string now contains hex representing utf8 codepoints + // Convert hex string to a Uint8Array + const bytes = new Uint8Array( + str.match(/.{1,2}/g)!.map((byte) => Number.parseInt(byte, 16)), + ); - // Decode the byte array to a string using UTF-8 - const decoder = new TextDecoder("utf-8") - return decoder.decode(bytes) + // Decode the byte array to a string using UTF-8 + const decoder = new TextDecoder("utf-8"); + return decoder.decode(bytes); } export function convertTextToHex(str: string): string { - if (!str.length) return str + if (!str.length) return str; - // Encode the string to a Uint8Array using UTF-8 - const encoder = new TextEncoder() - const bytes = encoder.encode(str) + // Encode the string to a Uint8Array using UTF-8 + const encoder = new TextEncoder(); + const bytes = encoder.encode(str); - // Convert the byte array to a hex string - return `0x${Array.from(bytes) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join("")}` + // Convert the byte array to a hex string + return `0x${Array.from(bytes) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("")}`; } export function ensureStarkFelt(value: string) { - if (!value.startsWith("0x")) { - return value - } - if (value.length < 66) { - return "0x" + value.replace("0x", "").padStart(64, "0") - } - return value + if (!value.startsWith("0x")) { + return value; + } + if (value.length < 66) { + return "0x" + value.replace("0x", "").padStart(64, "0"); + } + return value; } function transliterate(str: string, charMap: Record = {}) { - return str - .split("") - .map((char) => charMap[char] || char) - .join("") + return str + .split("") + .map((char) => charMap[char] || char) + .join(""); } const charMap: Record = { - á: "a", - ú: "u", - é: "e", - ä: "a", - Š: "S", - Ï: "I", - š: "s", - Í: "I", - í: "i", - ó: "o", - ï: "i", - ë: "e", - ê: "e", - â: "a", - Ó: "O", - ü: "u", - Á: "A", - Ü: "U", - ô: "o", - ž: "z", - Ê: "E", - ö: "o", - č: "c", - Â: "A", - Ä: "A", - Ë: "E", - É: "E", - Č: "C", - Ž: "Z", - Ö: "O", - Ú: "U", - Ô: "O", - "‘": "'", -} + á: "a", + ú: "u", + é: "e", + ä: "a", + Š: "S", + Ï: "I", + š: "s", + Í: "I", + í: "i", + ó: "o", + ï: "i", + ë: "e", + ê: "e", + â: "a", + Ó: "O", + ü: "u", + Á: "A", + Ü: "U", + ô: "o", + ž: "z", + Ê: "E", + ö: "o", + č: "c", + Â: "A", + Ä: "A", + Ë: "E", + É: "E", + Č: "C", + Ž: "Z", + Ö: "O", + Ú: "U", + Ô: "O", + "‘": "'", +}; function accentsToAscii(str: string): string { - // Character map for transliteration to ASCII - return transliterate(str, charMap) + // Character map for transliteration to ASCII + return transliterate(str, charMap); } export function toValidAscii(str: string): string { - const intermediateString = str.normalize("NFD").replace(/[\u0300-\u036f]/g, "") - return accentsToAscii(intermediateString) + const intermediateString = str + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); + return accentsToAscii(intermediateString); } export function shortAddress(address: string): string { - return `${address.slice(0, 6)}...${address.slice(-4)}` + return `${address.slice(0, 6)}...${address.slice(-4)}`; } diff --git a/packages/core-dojo/tsup.config.ts b/packages/core-dojo/tsup.config.ts index e9681a9..ad1a106 100644 --- a/packages/core-dojo/tsup.config.ts +++ b/packages/core-dojo/tsup.config.ts @@ -1,8 +1,12 @@ -import { type Options, defineConfig } from "tsup" +import { type Options, defineConfig } from "tsup"; -import { tsupConfig } from "../../tsup.config" +import { tsupConfig } from "../../tsup.config"; export default defineConfig({ - ...(tsupConfig as Options), - entry: ["src/index.ts", "src/DojoEngine.ts", "src/DojoSqlPixelStore.webworker.js"], -}) + ...(tsupConfig as Options), + entry: [ + "src/index.ts", + "src/DojoEngine.ts", + "src/DojoSqlPixelStore.webworker.js", + ], +}); diff --git a/packages/core-mud/src/MudEngine.ts b/packages/core-mud/src/MudEngine.ts index fc1e170..030c760 100644 --- a/packages/core-mud/src/MudEngine.ts +++ b/packages/core-mud/src/MudEngine.ts @@ -1,44 +1,44 @@ import type { - App, - Engine, - EngineStatus, - Engines, - Interaction, - Pixel, - PixelawCore, - QueueItem, - InteractParams, - Coordinate, -} from "@pixelaw/core" + App, + Engine, + EngineStatus, + Engines, + Interaction, + Pixel, + PixelawCore, + QueueItem, + InteractParams, + Coordinate, +} from "@pixelaw/core"; export type MudConfig = { - todo: number -} + todo: number; +}; -const ENGINE_ID = "mud" +const ENGINE_ID = "mud"; export class MudEngine implements Engine { - id: Engines = ENGINE_ID - status: EngineStatus = "uninitialized" - config: MudConfig = null! - core: PixelawCore + id: Engines = ENGINE_ID; + status: EngineStatus = "uninitialized"; + config: MudConfig = null!; + core: PixelawCore; - constructor(core: PixelawCore) { - this.core = core - } - async init(config: MudConfig) { - console.log("MudEngine init", config, this.constructor.name) - } + constructor(core: PixelawCore) { + this.core = core; + } + async init(config: MudConfig) { + console.log("MudEngine init", config, this.constructor.name); + } - async prepInteraction(_coordinate: Coordinate): Promise { - return {} as unknown as Interaction - } + async prepInteraction(_coordinate: Coordinate): Promise { + return {} as unknown as Interaction; + } - async executeInteraction(_interaction: Interaction): Promise { - console.log("TODO executeInteraction") - } + async executeInteraction(_interaction: Interaction): Promise { + console.log("TODO executeInteraction"); + } - executeQueueItem(_queueItem: QueueItem): Promise { - return Promise.resolve(false) - } + executeQueueItem(_queueItem: QueueItem): Promise { + return Promise.resolve(false); + } } diff --git a/packages/core-mud/src/index.ts b/packages/core-mud/src/index.ts index 472c2c7..b1c3ca2 100644 --- a/packages/core-mud/src/index.ts +++ b/packages/core-mud/src/index.ts @@ -1 +1 @@ -export * from "./MudEngine.ts" \ No newline at end of file +export * from "./MudEngine.ts"; diff --git a/packages/core-mud/tsup.config.ts b/packages/core-mud/tsup.config.ts index e9681a9..ad1a106 100644 --- a/packages/core-mud/tsup.config.ts +++ b/packages/core-mud/tsup.config.ts @@ -1,8 +1,12 @@ -import { type Options, defineConfig } from "tsup" +import { type Options, defineConfig } from "tsup"; -import { tsupConfig } from "../../tsup.config" +import { tsupConfig } from "../../tsup.config"; export default defineConfig({ - ...(tsupConfig as Options), - entry: ["src/index.ts", "src/DojoEngine.ts", "src/DojoSqlPixelStore.webworker.js"], -}) + ...(tsupConfig as Options), + entry: [ + "src/index.ts", + "src/DojoEngine.ts", + "src/DojoSqlPixelStore.webworker.js", + ], +}); diff --git a/packages/core/src/PixelawCore.ts b/packages/core/src/PixelawCore.ts index 8aeed8e..83b31ba 100644 --- a/packages/core/src/PixelawCore.ts +++ b/packages/core/src/PixelawCore.ts @@ -1,293 +1,307 @@ import type { - NotificationStore, - AppStore, - BaseWallet, - Coordinate, - CoreDefaults, - CoreStatus, - Engine, - EngineConstructor, - Executor, - Interaction, - InteractParams, - Pixel, - PixelCoreEvents, - PixelStore, - QueueItem, - QueueStore, - TileStore, - UpdateService, - Wallet, - WorldConfig, - WorldsRegistry, -} from "./types.ts" - -import mitt from "mitt" -import { createStorage, type Storage, type StorageValue } from "unstorage" -import nullDriver from "unstorage/drivers/null" -import Canvas2DRenderer from "./renderers/Canvas2DRenderer" + NotificationStore, + AppStore, + BaseWallet, + Coordinate, + CoreDefaults, + CoreStatus, + Engine, + EngineConstructor, + Executor, + Interaction, + InteractParams, + Pixel, + PixelCoreEvents, + PixelStore, + QueueItem, + QueueStore, + TileStore, + UpdateService, + Wallet, + WorldConfig, + WorldsRegistry, +} from "./types.ts"; + +import mitt from "mitt"; +import { createStorage, type Storage, type StorageValue } from "unstorage"; +import nullDriver from "unstorage/drivers/null"; +import Canvas2DRenderer from "./renderers/Canvas2DRenderer"; export class PixelawCore { - _status: CoreStatus = "uninitialized" - worldConfig: WorldConfig = null! - _engine: Engine = null! - pixelStore: PixelStore = null! - tileStore: TileStore = null! - appStore: AppStore = null! - updateService: UpdateService = null! - viewPort: Canvas2DRenderer = null! - events = mitt() - queue: QueueStore = null! - executor: Executor | null = null! - notification: NotificationStore | null = null! - - private _worldsRegistry: WorldsRegistry - private _app: string | null = null - private _color = 0 - private _zoom = 1 - private _lastNotification = 0 - private _center: Coordinate = [0, 0] - private _world: string - - private engines: Record> = {} - - private _wallet: Wallet | BaseWallet | null = null - private _account: unknown | null - readonly storage: Storage - - constructor( - engines: Record>, - worldsRegistry: WorldsRegistry, - storage: Storage = createStorage({ driver: nullDriver() }), - ) { - this.engines = engines - - this._worldsRegistry = worldsRegistry - - this.storage = storage + _status: CoreStatus = "uninitialized"; + worldConfig: WorldConfig = null!; + _engine: Engine = null!; + pixelStore: PixelStore = null!; + tileStore: TileStore = null!; + appStore: AppStore = null!; + updateService: UpdateService = null!; + viewPort: Canvas2DRenderer = null!; + events = mitt(); + queue: QueueStore = null!; + executor: Executor | null = null!; + notification: NotificationStore | null = null!; + + private _worldsRegistry: WorldsRegistry; + private _app: string | null = null; + private _color = 0; + private _zoom = 1; + private _lastNotification = 0; + private _center: Coordinate = [0, 0]; + private _world: string; + + private engines: Record> = {}; + + private _wallet: Wallet | BaseWallet | null = null; + private _account: unknown | null; + readonly storage: Storage; + + constructor( + engines: Record>, + worldsRegistry: WorldsRegistry, + storage: Storage = createStorage({ driver: nullDriver() }), + ) { + this.engines = engines; + + this._worldsRegistry = worldsRegistry; + + this.storage = storage; + } + + public set account(newAccount: unknown | null) { + this._account = newAccount; + this.events.emit("accountChanged", newAccount); + console.log("core.account", newAccount); + if (newAccount) { + this.status = "ready"; + this.executor.account = newAccount; + } else { + this.status = "readyWithoutWallet"; } - - public set account(newAccount: unknown | null) { - this._account = newAccount - this.events.emit("accountChanged", newAccount) - console.log("core.account", newAccount) - if (newAccount) { - this.status = "ready" - this.executor.account = newAccount - } else { - this.status = "readyWithoutWallet" - } - } - public get account() { - return this._account - } - - public getExecutor(): Executor | null { - return this.executor - } - - public get wallet(): Wallet | BaseWallet | null { - return this._wallet - } - - public set wallet(wallet: Wallet | null) { - if (wallet === this._wallet) return - - this._wallet = wallet - console.log("setWallet", this._wallet) - this.events.emit("walletChanged", wallet) - - this.storage.setItem(this.getStorageKey("wallet"), JSON.stringify(wallet)).catch(console.error) - - // Also set account to null, since it relies on wallet - // this.account = account - } - - public get engineId(): string | null { - return this._engine ? this._engine.constructor.name : null - } - - public get engine(): Engine | null { - return this._engine - } - - private async getStorageDefaults(): Promise { - // Use Promise.all to fetch all items concurrently, improving time efficiency - const [app, color, center, zoom] = await Promise.all([ - this.storage.getItem(this.getStorageKey("app")), - this.storage.getItem(this.getStorageKey("color")), - this.storage.getItem(this.getStorageKey("center")), - this.storage.getItem(this.getStorageKey("zoom")), - ]) - - // Check for undefined values directly, improving readability - if (app !== null && color !== null && center !== null && zoom !== null) { - return { - app: app as string, - color: color as unknown as number, - center: center as unknown as number[], - zoom: zoom as unknown as number, - } - } - } - - public async loadWorld(world: string, urlDefaults?: CoreDefaults) { - console.log("loading loadWorld") - - if (!Object.prototype.hasOwnProperty.call(this._worldsRegistry, world)) - throw Error(`World ${world} does not exist in registry`) - - this.status = "loadConfig" - const worldConfig = this._worldsRegistry[world] - - const engineClass = this.engines[worldConfig.engine.toLowerCase()] - - if (!engineClass) { - throw new Error(`Unsupported engine: ${worldConfig.engine}`) - } - - this._engine = new engineClass(this) - - this.status = "initEngine" - - // Engine init will access some core setters, so stuff may change - await this._engine.init(worldConfig.config) - - this.world = world - - const storageDefaults = await this.getStorageDefaults() - - const defaults = urlDefaults ?? storageDefaults ?? worldConfig.defaults - if (defaults) { - this.app = defaults.app - this.color = defaults.color - this.center = defaults.center as Coordinate - this.zoom = defaults.zoom - } - - // TODO load lastNotification? - - this.viewPort = new Canvas2DRenderer(this) - - this.worldConfig = worldConfig - - this.events.emit("engineChanged", this._engine) - // Try to get the Wallet - const baseWallet = await this.storage.getItem(this.getStorageKey("wallet")) - if (baseWallet) { - console.log("loading basewallet") - this.wallet = baseWallet as unknown as Wallet - - this.status = "initAccount" - } else { - this.status = "readyWithoutWallet" - } - - this.events.on("centerChanged", (newCenter: Coordinate) => { - this.center = newCenter - }) - - this.events.on("zoomChanged", (newZoom: number) => { - this.zoom = newZoom - }) + } + public get account() { + return this._account; + } + + public getExecutor(): Executor | null { + return this.executor; + } + + public get wallet(): Wallet | BaseWallet | null { + return this._wallet; + } + + public set wallet(wallet: Wallet | null) { + if (wallet === this._wallet) return; + + this._wallet = wallet; + console.log("setWallet", this._wallet); + this.events.emit("walletChanged", wallet); + + this.storage + .setItem(this.getStorageKey("wallet"), JSON.stringify(wallet)) + .catch(console.error); + + // Also set account to null, since it relies on wallet + // this.account = account + } + + public get engineId(): string | null { + return this._engine ? this._engine.constructor.name : null; + } + + public get engine(): Engine | null { + return this._engine; + } + + private async getStorageDefaults(): Promise { + // Use Promise.all to fetch all items concurrently, improving time efficiency + const [app, color, center, zoom] = await Promise.all([ + this.storage.getItem(this.getStorageKey("app")), + this.storage.getItem(this.getStorageKey("color")), + this.storage.getItem(this.getStorageKey("center")), + this.storage.getItem(this.getStorageKey("zoom")), + ]); + + // Check for undefined values directly, improving readability + if (app !== null && color !== null && center !== null && zoom !== null) { + return { + app: app as string, + color: color as unknown as number, + center: center as unknown as number[], + zoom: zoom as unknown as number, + }; } + } - public get status(): CoreStatus { - return this._status - } - private set status(newStatus: CoreStatus) { - this._status = newStatus - this.events.emit("statusChanged", newStatus) + public async loadWorld(world: string, urlDefaults?: CoreDefaults) { + console.log("loading loadWorld"); - console.info("core.setStatus", newStatus) - } + if (!Object.prototype.hasOwnProperty.call(this._worldsRegistry, world)) + throw Error(`World ${world} does not exist in registry`); - public get worldsRegistry(): WorldsRegistry { - return this._worldsRegistry - } + this.status = "loadConfig"; + const worldConfig = this._worldsRegistry[world]; - public get app(): string | null { - return this._app - } + const engineClass = this.engines[worldConfig.engine.toLowerCase()]; - public set app(newApp: string | null) { - this._app = newApp - this.events.emit("appChanged", newApp) - this.storage.setItem(this.getStorageKey("app"), newApp).catch(console.error) + if (!engineClass) { + throw new Error(`Unsupported engine: ${worldConfig.engine}`); } - private set world(newWorld: string) { - this._world = newWorld - this.storage.setItem(this.getStorageKey("world"), newWorld).catch(console.error) - this.events.emit("worldChanged", newWorld) - } + this._engine = new engineClass(this); - public get color(): number { - return this._color - } + this.status = "initEngine"; - public set color(newColor: number | null) { - this._color = newColor - this.events.emit("colorChanged", newColor) - this.storage.setItem(this.getStorageKey("color"), newColor.toString()).catch(console.error) - } + // Engine init will access some core setters, so stuff may change + await this._engine.init(worldConfig.config); - public get zoom(): number { - return this._zoom - } + this.world = world; - public set zoom(newZoom: number) { - if (this._zoom === newZoom) return - this._zoom = newZoom - this.events.emit("zoomChanged", newZoom) - this.storage.setItem(this.getStorageKey("zoom"), newZoom.toString()).catch(console.error) - } + const storageDefaults = await this.getStorageDefaults(); - public set lastNotification(newLastNotification: number) { - if (this._lastNotification === newLastNotification) return - this._lastNotification = newLastNotification - this.storage.setItem(this.getStorageKey("lastNotification"), newLastNotification).catch(console.error) + const defaults = urlDefaults ?? storageDefaults ?? worldConfig.defaults; + if (defaults) { + this.app = defaults.app; + this.color = defaults.color; + this.center = defaults.center as Coordinate; + this.zoom = defaults.zoom; } - public get lastNotification(): number { - return this._lastNotification - } + // TODO load lastNotification? - public get center(): Coordinate { - return this._center - } + this.viewPort = new Canvas2DRenderer(this); - public set center(newCenter: Coordinate) { - if (this._center === newCenter) return - this._center = newCenter - this.events.emit("centerChanged", newCenter) - this.storage.setItem(this.getStorageKey("center"), JSON.stringify(newCenter)).catch(console.error) - } + this.worldConfig = worldConfig; - public get world(): string { - return this._world - } - - public async prepInteraction(coordinate: Coordinate): Promise { - return await this._engine.prepInteraction(coordinate) - } + this.events.emit("engineChanged", this._engine); + // Try to get the Wallet + const baseWallet = await this.storage.getItem(this.getStorageKey("wallet")); + if (baseWallet) { + console.log("loading basewallet"); + this.wallet = baseWallet as unknown as Wallet; - // public async executeInteraction(interaction: Interaction): Promise { - // return await this.engine.executeInteraction(interaction) - // } - - // public async handleInteraction(coordinate: Coordinate): Promise { - // const pixel = this.pixelStore.getPixel(coordinate) ?? ({ x: coordinate[0], y: coordinate[1] } as Pixel) - // const app = this.appStore.getByName(this.app) - // return await this.engine.handleInteraction(app, pixel, this.color) - // } - - // TODO finalize return type etc - public async executeQueueItem(queueItem: QueueItem): Promise { - return await this._engine.executeQueueItem(queueItem) + this.status = "initAccount"; + } else { + this.status = "readyWithoutWallet"; } - private getStorageKey(key: string): string { - return `${this._world}::${key}` - } + this.events.on("centerChanged", (newCenter: Coordinate) => { + this.center = newCenter; + }); + + this.events.on("zoomChanged", (newZoom: number) => { + this.zoom = newZoom; + }); + } + + public get status(): CoreStatus { + return this._status; + } + private set status(newStatus: CoreStatus) { + this._status = newStatus; + this.events.emit("statusChanged", newStatus); + + console.info("core.setStatus", newStatus); + } + + public get worldsRegistry(): WorldsRegistry { + return this._worldsRegistry; + } + + public get app(): string | null { + return this._app; + } + + public set app(newApp: string | null) { + this._app = newApp; + this.events.emit("appChanged", newApp); + this.storage + .setItem(this.getStorageKey("app"), newApp) + .catch(console.error); + } + + private set world(newWorld: string) { + this._world = newWorld; + this.storage + .setItem(this.getStorageKey("world"), newWorld) + .catch(console.error); + this.events.emit("worldChanged", newWorld); + } + + public get color(): number { + return this._color; + } + + public set color(newColor: number | null) { + this._color = newColor; + this.events.emit("colorChanged", newColor); + this.storage + .setItem(this.getStorageKey("color"), newColor.toString()) + .catch(console.error); + } + + public get zoom(): number { + return this._zoom; + } + + public set zoom(newZoom: number) { + if (this._zoom === newZoom) return; + this._zoom = newZoom; + this.events.emit("zoomChanged", newZoom); + this.storage + .setItem(this.getStorageKey("zoom"), newZoom.toString()) + .catch(console.error); + } + + public set lastNotification(newLastNotification: number) { + if (this._lastNotification === newLastNotification) return; + this._lastNotification = newLastNotification; + this.storage + .setItem(this.getStorageKey("lastNotification"), newLastNotification) + .catch(console.error); + } + + public get lastNotification(): number { + return this._lastNotification; + } + + public get center(): Coordinate { + return this._center; + } + + public set center(newCenter: Coordinate) { + if (this._center === newCenter) return; + this._center = newCenter; + this.events.emit("centerChanged", newCenter); + this.storage + .setItem(this.getStorageKey("center"), JSON.stringify(newCenter)) + .catch(console.error); + } + + public get world(): string { + return this._world; + } + + public async prepInteraction(coordinate: Coordinate): Promise { + return await this._engine.prepInteraction(coordinate); + } + + // public async executeInteraction(interaction: Interaction): Promise { + // return await this.engine.executeInteraction(interaction) + // } + + // public async handleInteraction(coordinate: Coordinate): Promise { + // const pixel = this.pixelStore.getPixel(coordinate) ?? ({ x: coordinate[0], y: coordinate[1] } as Pixel) + // const app = this.appStore.getByName(this.app) + // return await this.engine.handleInteraction(app, pixel, this.color) + // } + + // TODO finalize return type etc + public async executeQueueItem(queueItem: QueueItem): Promise { + return await this._engine.executeQueueItem(queueItem); + } + + private getStorageKey(key: string): string { + return `${this._world}::${key}`; + } } diff --git a/packages/core/src/common/AblyUpdateService.ts b/packages/core/src/common/AblyUpdateService.ts index 3fe666b..62c03f0 100644 --- a/packages/core/src/common/AblyUpdateService.ts +++ b/packages/core/src/common/AblyUpdateService.ts @@ -1,34 +1,36 @@ -import Ably, { type RealtimeChannel } from "ably" -import type { PixelawCore } from "src/PixelawCore.ts" -import type { Bounds, UpdateService } from "../types.ts" +import Ably, { type RealtimeChannel } from "ably"; +import type { PixelawCore } from "src/PixelawCore.ts"; +import type { Bounds, UpdateService } from "../types.ts"; export class AblyUpdateService implements UpdateService { - private _ably: Ably.Realtime - private _channel: RealtimeChannel - private _core: PixelawCore - // - constructor(core: PixelawCore) { - this._core = core - this._ably = new Ably.Realtime("5H4sWg.qL6dfg:D1nIhqTdqDQ7wgXW5mXQ5TbGilfGgDicYEjLPNTQmrI") + private _ably: Ably.Realtime; + private _channel: RealtimeChannel; + private _core: PixelawCore; + // + constructor(core: PixelawCore) { + this._core = core; + this._ably = new Ably.Realtime( + "5H4sWg.qL6dfg:D1nIhqTdqDQ7wgXW5mXQ5TbGilfGgDicYEjLPNTQmrI", + ); - this._ably.connection.once("connected", () => { - console.log("Connected to Ably!") - }) + this._ably.connection.once("connected", () => { + console.log("Connected to Ably!"); + }); - this._channel = this._ably.channels.get(core.world) + this._channel = this._ably.channels.get(core.world); - this._channel - .subscribe("PixelUpdate", (message) => { - console.log("PixelUpdate received: " + message.data) - const pixels = JSON.parse(message.data) - this._core.pixelStore.setPixels(pixels) - }) - .then() - } + this._channel + .subscribe("PixelUpdate", (message) => { + console.log("PixelUpdate received: " + message.data); + const pixels = JSON.parse(message.data); + this._core.pixelStore.setPixels(pixels); + }) + .then(); + } - get channel() { - return this._channel - } + get channel() { + return this._channel; + } - public setBounds(_b: Bounds): void {} + public setBounds(_b: Bounds): void {} } diff --git a/packages/core/src/common/WsUpdateService.ts b/packages/core/src/common/WsUpdateService.ts index 3400dcf..9ff8a8c 100644 --- a/packages/core/src/common/WsUpdateService.ts +++ b/packages/core/src/common/WsUpdateService.ts @@ -1,81 +1,87 @@ -import { calculateTileBounds } from "../renderers/Canvas2DRenderer/utils.ts" -import { type Bounds, type UpdateService } from "../types.ts" -import { areBoundsEqual } from "../utils.ts" +import { calculateTileBounds } from "../renderers/Canvas2DRenderer/utils.ts"; +import { type Bounds, type UpdateService } from "../types.ts"; +import { areBoundsEqual } from "../utils.ts"; type Message = { - cmd: string - data: unknown | TileChangedMessage -} + cmd: string; + data: unknown | TileChangedMessage; +}; type TileChangedMessage = { - tileName: string - timestamp: number -} + tileName: string; + timestamp: number; +}; export class WsUpdateService implements UpdateService { - private tileChanged: TileChangedMessage | null = null - private bounds: Bounds | null = null - private socket: WebSocket | null = null - private url: string | undefined + private tileChanged: TileChangedMessage | null = null; + private bounds: Bounds | null = null; + private socket: WebSocket | null = null; + private url: string | undefined; - constructor(url: string | undefined) { - this.url = url - if (url) { - this.initializeSocket(url) - } + constructor(url: string | undefined) { + this.url = url; + if (url) { + this.initializeSocket(url); } + } - private initializeSocket(url: string): void { - if (!this.socket) { - this.socket = new WebSocket(`${url.replace("https", "wss")}/tiles`) - this.socket.onerror = () => { - console.log("err") - } + private initializeSocket(url: string): void { + if (!this.socket) { + this.socket = new WebSocket(`${url.replace("https", "wss")}/tiles`); + this.socket.onerror = () => { + console.log("err"); + }; - this.socket.onopen = () => { - if (this.bounds) { - const message = JSON.stringify({ cmd: "subscribe", data: { boundingBox: this.bounds } }) - this.socket?.send(message) - } - } + this.socket.onopen = () => { + if (this.bounds) { + const message = JSON.stringify({ + cmd: "subscribe", + data: { boundingBox: this.bounds }, + }); + this.socket?.send(message); + } + }; - this.socket.onclose = () => { - this.socket = null - setTimeout(() => this.initializeSocket(url), 10000) - } + this.socket.onclose = () => { + this.socket = null; + setTimeout(() => this.initializeSocket(url), 10000); + }; - this.socket.onmessage = (event) => { - const msg: Message = JSON.parse(event.data) + this.socket.onmessage = (event) => { + const msg: Message = JSON.parse(event.data); - if (msg.cmd === "tileChanged") { - const tileChangedMsg = msg.data as TileChangedMessage - this.tileChanged = tileChangedMsg - } else { - console.log("Unrecognized message from ws: ", msg) - } - } + if (msg.cmd === "tileChanged") { + const tileChangedMsg = msg.data as TileChangedMessage; + this.tileChanged = tileChangedMsg; + } else { + console.log("Unrecognized message from ws: ", msg); } + }; } + } - public setBounds(b: Bounds): void { - let newBounds = b - if (!this.url) return + public setBounds(b: Bounds): void { + let newBounds = b; + if (!this.url) return; - if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { - this.initializeSocket(this.url) - } else { - newBounds = calculateTileBounds(newBounds) - - if (!this.bounds || !areBoundsEqual(newBounds, this.bounds)) { - const message = JSON.stringify({ cmd: "subscribe", data: { boundingBox: newBounds } }) - this.socket?.send(message) - } - } + if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { + this.initializeSocket(this.url); + } else { + newBounds = calculateTileBounds(newBounds); - this.bounds = newBounds + if (!this.bounds || !areBoundsEqual(newBounds, this.bounds)) { + const message = JSON.stringify({ + cmd: "subscribe", + data: { boundingBox: newBounds }, + }); + this.socket?.send(message); + } } - public getTileChanged(): TileChangedMessage | null { - return this.tileChanged - } + this.bounds = newBounds; + } + + public getTileChanged(): TileChangedMessage | null { + return this.tileChanged; + } } diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 8d806e3..6ca5376 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -1,5 +1,6 @@ -export const PUBLIC_NODE_URL: string = "http://localhost:5050" +export const PUBLIC_NODE_URL: string = "http://localhost:5050"; -export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000000000000000000000000000" -export const NAMESPACE = "pixelaw" -export const DEFAULT_WORLD = "local-empty" \ No newline at end of file +export const ZERO_ADDRESS = + "0x0000000000000000000000000000000000000000000000000000000000000000"; +export const NAMESPACE = "pixelaw"; +export const DEFAULT_WORLD = "local-empty"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0671bc2..8e9b801 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,9 +1,9 @@ // export { RestTileStore } from "./common/RestTileStore.ts" // export { WsUpdateService } from "./common/WsUpdateService.ts" -export { AblyUpdateService } from "./common/AblyUpdateService.ts" -export { NAMESPACE } from "./constants.ts" -export * from "./PixelawCore.ts" -export * from "./renderers/Canvas2DRenderer/types.ts" -export * from "./types.ts" -export * from "./utils.ts" +export { AblyUpdateService } from "./common/AblyUpdateService.ts"; +export { NAMESPACE } from "./constants.ts"; +export * from "./PixelawCore.ts"; +export * from "./renderers/Canvas2DRenderer/types.ts"; +export * from "./types.ts"; +export * from "./utils.ts"; diff --git a/packages/core/src/renderers/Canvas2DRenderer/effects.ts b/packages/core/src/renderers/Canvas2DRenderer/effects.ts index 06dad45..2ac33a2 100644 --- a/packages/core/src/renderers/Canvas2DRenderer/effects.ts +++ b/packages/core/src/renderers/Canvas2DRenderer/effects.ts @@ -1,163 +1,179 @@ -import type { Coordinate } from "../../types" -import { hexToRgb } from "../../utils" -import { GlowingCell } from "./types" -import type Canvas2DRenderer from "./index.ts" +import type { Coordinate } from "../../types"; +import { hexToRgb } from "../../utils"; +import { GlowingCell } from "./types"; +import type Canvas2DRenderer from "./index.ts"; /** * Effects management for Canvas2DRenderer * This file contains methods for managing visual effects like glowing cells and inertia */ export class EffectsManager { - private renderer: Canvas2DRenderer - - constructor(renderer: Canvas2DRenderer) { - this.renderer = renderer - } - - /** - * Starts a glow effect on a specific cell - */ - public addGlow(coordinate: Coordinate, duration: number, htmlColor: string, intensity: number, size: number) { - const rgb = hexToRgb(htmlColor) - - const key = `${coordinate[0]},${coordinate[1]}` - const existingCell = this.renderer.glowingCells.get(key) - if (existingCell) { - existingCell.startTime = Date.now() - existingCell.duration = duration - } else { - this.renderer.glowingCells.set(key, { - intensity, - duration, - startTime: Date.now(), - color: rgb, - size, - }) - } - if (!this.renderer.glowInterval) { - this.startGlowInterval() - } + private renderer: Canvas2DRenderer; + + constructor(renderer: Canvas2DRenderer) { + this.renderer = renderer; + } + + /** + * Starts a glow effect on a specific cell + */ + public addGlow( + coordinate: Coordinate, + duration: number, + htmlColor: string, + intensity: number, + size: number, + ) { + const rgb = hexToRgb(htmlColor); + + const key = `${coordinate[0]},${coordinate[1]}`; + const existingCell = this.renderer.glowingCells.get(key); + if (existingCell) { + existingCell.startTime = Date.now(); + existingCell.duration = duration; + } else { + this.renderer.glowingCells.set(key, { + intensity, + duration, + startTime: Date.now(), + color: rgb, + size, + }); } - - /** - * Starts the interval for updating glow effects - */ - public startGlowInterval() { - this.renderer.glowInterval = setInterval(() => { - const currentTime = Date.now() - for (const [key, cell] of this.renderer.glowingCells.entries()) { - const elapsed = currentTime - cell.startTime - cell.intensity = Math.max(0, 1 - elapsed / cell.duration) - if (cell.intensity <= 0) { - this.renderer.glowingCells.delete(key) - } - } - - if (this.renderer.glowingCells.size === 0) { - clearInterval(this.renderer.glowInterval!) - this.renderer.glowInterval = null - } - - // this.requestRender() - this.renderer.needRender = true - }, 50) // 20fps + if (!this.renderer.glowInterval) { + this.startGlowInterval(); } - - // Add a method to handle zoom inertia - public handleZoomInertia(): void { - if (!this.renderer.isZoomInertiaActive) return - - if (Math.abs(this.renderer.zoomInertiaVelocity) < 0.001) { - this.renderer.isZoomInertiaActive = false - this.renderer.zoomInertiaVelocity = 0 - return + } + + /** + * Starts the interval for updating glow effects + */ + public startGlowInterval() { + this.renderer.glowInterval = setInterval(() => { + const currentTime = Date.now(); + for (const [key, cell] of this.renderer.glowingCells.entries()) { + const elapsed = currentTime - cell.startTime; + cell.intensity = Math.max(0, 1 - elapsed / cell.duration); + if (cell.intensity <= 0) { + this.renderer.glowingCells.delete(key); } - - // Calculate new zoom level - const newZoom = Math.max( - this.renderer.options.minZoom, - Math.min(this.renderer.options.maxZoom, this.renderer.zoom * (1 + this.renderer.zoomInertiaVelocity)), - ) - - // Calculate the midpoint of the canvas as the zoom center - const midX = this.renderer.canvas.width / 2 - const midY = this.renderer.canvas.height / 2 - - // Calculate world coordinates before zoom - const worldXBefore = (midX - this.renderer.offsetX) / this.renderer.zoom - const worldYBefore = (midY - this.renderer.offsetY) / this.renderer.zoom - - // Calculate world coordinates after zoom - const worldXAfter = (midX - this.renderer.offsetX) / newZoom - const worldYAfter = (midY - this.renderer.offsetY) / newZoom - - // Adjust offset to keep the midpoint fixed - this.renderer.offsetX += (worldXAfter - worldXBefore) * newZoom - this.renderer.offsetY += (worldYAfter - worldYBefore) * newZoom - - // Update zoom and apply deceleration - this.renderer.zoom = newZoom - this.renderer.zoomInertiaVelocity *= this.renderer.decelerationFactor - - this.renderer.needRender = true - this.renderer.updateCenter() - this.renderer.updateBounds() + } + + if (this.renderer.glowingCells.size === 0) { + clearInterval(this.renderer.glowInterval!); + this.renderer.glowInterval = null; + } + + // this.requestRender() + this.renderer.needRender = true; + }, 50); // 20fps + } + + // Add a method to handle zoom inertia + public handleZoomInertia(): void { + if (!this.renderer.isZoomInertiaActive) return; + + if (Math.abs(this.renderer.zoomInertiaVelocity) < 0.001) { + this.renderer.isZoomInertiaActive = false; + this.renderer.zoomInertiaVelocity = 0; + return; } - // Modify the startZoomInertia method - public startZoomInertia(): void { - if (Math.abs(this.renderer.zoomInertiaVelocity) >= 0.001) { - this.renderer.isZoomInertiaActive = true - } + // Calculate new zoom level + const newZoom = Math.max( + this.renderer.options.minZoom, + Math.min( + this.renderer.options.maxZoom, + this.renderer.zoom * (1 + this.renderer.zoomInertiaVelocity), + ), + ); + + // Calculate the midpoint of the canvas as the zoom center + const midX = this.renderer.canvas.width / 2; + const midY = this.renderer.canvas.height / 2; + + // Calculate world coordinates before zoom + const worldXBefore = (midX - this.renderer.offsetX) / this.renderer.zoom; + const worldYBefore = (midY - this.renderer.offsetY) / this.renderer.zoom; + + // Calculate world coordinates after zoom + const worldXAfter = (midX - this.renderer.offsetX) / newZoom; + const worldYAfter = (midY - this.renderer.offsetY) / newZoom; + + // Adjust offset to keep the midpoint fixed + this.renderer.offsetX += (worldXAfter - worldXBefore) * newZoom; + this.renderer.offsetY += (worldYAfter - worldYBefore) * newZoom; + + // Update zoom and apply deceleration + this.renderer.zoom = newZoom; + this.renderer.zoomInertiaVelocity *= this.renderer.decelerationFactor; + + this.renderer.needRender = true; + this.renderer.updateCenter(); + this.renderer.updateBounds(); + } + + // Modify the startZoomInertia method + public startZoomInertia(): void { + if (Math.abs(this.renderer.zoomInertiaVelocity) >= 0.001) { + this.renderer.isZoomInertiaActive = true; } - - public startPanInertia(): void { - if (Math.abs(this.renderer.panInertiaVelocity[0]) < 4 && Math.abs(this.renderer.panInertiaVelocity[1]) < 4) { - return - } - - this.renderer.isPanInertiaActive = true + } + + public startPanInertia(): void { + if ( + Math.abs(this.renderer.panInertiaVelocity[0]) < 4 && + Math.abs(this.renderer.panInertiaVelocity[1]) < 4 + ) { + return; } - public stopPanInertia(): void { - this.renderer.isPanInertiaActive = false - this.renderer.panInertiaVelocity = [0, 0] - } + this.renderer.isPanInertiaActive = true; + } - public handlePanInertia(): void { - if (!this.renderer.isPanInertiaActive) return + public stopPanInertia(): void { + this.renderer.isPanInertiaActive = false; + this.renderer.panInertiaVelocity = [0, 0]; + } - // Apply velocity to offset - this.renderer.offsetX += this.renderer.panInertiaVelocity[0] - this.renderer.offsetY += this.renderer.panInertiaVelocity[1] + public handlePanInertia(): void { + if (!this.renderer.isPanInertiaActive) return; - // Decelerate - this.renderer.panInertiaVelocity[0] *= this.renderer.decelerationFactor - this.renderer.panInertiaVelocity[1] *= this.renderer.decelerationFactor + // Apply velocity to offset + this.renderer.offsetX += this.renderer.panInertiaVelocity[0]; + this.renderer.offsetY += this.renderer.panInertiaVelocity[1]; - // Stop inertia when velocity is low - if ( - Math.abs(this.renderer.panInertiaVelocity[0]) < 0.01 && - Math.abs(this.renderer.panInertiaVelocity[1]) < 0.01 - ) { - this.stopPanInertia() - } + // Decelerate + this.renderer.panInertiaVelocity[0] *= this.renderer.decelerationFactor; + this.renderer.panInertiaVelocity[1] *= this.renderer.decelerationFactor; - this.renderer.needRender = true - this.renderer.updateCenter() - this.renderer.updateBounds() + // Stop inertia when velocity is low + if ( + Math.abs(this.renderer.panInertiaVelocity[0]) < 0.01 && + Math.abs(this.renderer.panInertiaVelocity[1]) < 0.01 + ) { + this.stopPanInertia(); } - public addNotification(coordinate: Coordinate, duration: number, text: string): void { - const key = `${coordinate[0]},${coordinate[1]}` - // this.renderer.activeNotifications.set(key, { - // startTime: Date.now(), - // duration, - // text, - // }) - - if (!this.renderer.glowInterval) { - this.startGlowInterval() - } + this.renderer.needRender = true; + this.renderer.updateCenter(); + this.renderer.updateBounds(); + } + + public addNotification( + coordinate: Coordinate, + duration: number, + text: string, + ): void { + const key = `${coordinate[0]},${coordinate[1]}`; + // this.renderer.activeNotifications.set(key, { + // startTime: Date.now(), + // duration, + // text, + // }) + + if (!this.renderer.glowInterval) { + this.startGlowInterval(); } + } } diff --git a/packages/core/src/renderers/Canvas2DRenderer/handlers.ts b/packages/core/src/renderers/Canvas2DRenderer/handlers.ts index 19abe71..41357e9 100644 --- a/packages/core/src/renderers/Canvas2DRenderer/handlers.ts +++ b/packages/core/src/renderers/Canvas2DRenderer/handlers.ts @@ -1,257 +1,310 @@ -import type { Coordinate } from "../../types" -import type Canvas2DRenderer from "./index.ts" +import type { Coordinate } from "../../types"; +import type Canvas2DRenderer from "./index.ts"; /** * Event handlers for Canvas2DRenderer * This file contains all the event handlers for mouse and touch interactions */ export class EventHandlers { - private renderer: Canvas2DRenderer - - constructor(renderer: Canvas2DRenderer) { - this.renderer = renderer - } - - /** - * Initializes event listeners for user interaction - */ - public initEventListeners(): void { - // Mouse wheel for zooming - this.renderer.canvas.addEventListener("wheel", this.handleWheel.bind(this)) - - // Mouse events for panning - this.renderer.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this)) - this.renderer.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this)) - this.renderer.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this)) - this.renderer.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this)) - - // Touch events for mobile - this.renderer.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this)) - this.renderer.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this)) - this.renderer.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this)) + private renderer: Canvas2DRenderer; + + constructor(renderer: Canvas2DRenderer) { + this.renderer = renderer; + } + + /** + * Initializes event listeners for user interaction + */ + public initEventListeners(): void { + // Mouse wheel for zooming + this.renderer.canvas.addEventListener("wheel", this.handleWheel.bind(this)); + + // Mouse events for panning + this.renderer.canvas.addEventListener( + "mousedown", + this.handleMouseDown.bind(this), + ); + this.renderer.canvas.addEventListener( + "mousemove", + this.handleMouseMove.bind(this), + ); + this.renderer.canvas.addEventListener( + "mouseup", + this.handleMouseUp.bind(this), + ); + this.renderer.canvas.addEventListener( + "mouseleave", + this.handleMouseUp.bind(this), + ); + + // Touch events for mobile + this.renderer.canvas.addEventListener( + "touchstart", + this.handleTouchStart.bind(this), + ); + this.renderer.canvas.addEventListener( + "touchmove", + this.handleTouchMove.bind(this), + ); + this.renderer.canvas.addEventListener( + "touchend", + this.handleTouchEnd.bind(this), + ); + } + + /** + * Handles mouse wheel events for zooming + */ + public handleWheel(event: WheelEvent): void { + event.preventDefault(); + + // Calculate zoom center (mouse position) + const rect = this.renderer.canvas.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + // Calculate world coordinates before zoom + const worldXBefore = (mouseX - this.renderer.offsetX) / this.renderer.zoom; + const worldYBefore = (mouseY - this.renderer.offsetY) / this.renderer.zoom; + + // Update zoom level + const zoomDelta = event.deltaY > 0 ? 0.9 : 1.1; + this.renderer.zoomInertiaVelocity = (zoomDelta - 1) * 0.1; // Adjust multiplier for desired speed + + const newZoom = Math.max( + this.renderer.options.minZoom, + Math.min(this.renderer.options.maxZoom, this.renderer.zoom * zoomDelta), + ); + + // Calculate world coordinates after zoom + const worldXAfter = (mouseX - this.renderer.offsetX) / newZoom; + const worldYAfter = (mouseY - this.renderer.offsetY) / newZoom; + + // Adjust offset to keep the point under the mouse fixed + this.renderer.offsetX += (worldXAfter - worldXBefore) * newZoom; + this.renderer.offsetY += (worldYAfter - worldYBefore) * newZoom; + + this.renderer.updateZoom(newZoom); + this.renderer.updateCenter(); + this.renderer.updateBounds(); + + // this.requestRender() + this.renderer.needRender = true; + this.renderer.effects.startZoomInertia(); + } + + /** + * Handles mouse down events for panning + */ + public handleMouseDown(event: MouseEvent): void { + event.preventDefault(); + this.renderer.effects.stopPanInertia(); + + this.renderer.dragStartTime = Date.now(); + this.renderer.dragStartCoord = [event.clientX, event.clientY]; + this.renderer.lastDragPoint = [event.clientX, event.clientY]; + + // this.renderer.canvas.style.cursor = "grabbing" + } + + /** + * Handles mouse move events for panning + */ + public handleMouseMove(event: MouseEvent): void { + // TODO when doing hover effects, this has to be changed + if (!this.renderer.dragStartTime) return; + + const currentTime = Date.now(); + const timeDiff = currentTime - this.renderer.lastDragTime; + this.renderer.lastDragTime = currentTime; + + // Calculate the distance moved + const deltaX = event.clientX - this.renderer.lastDragPoint[0]; + const deltaY = event.clientY - this.renderer.lastDragPoint[1]; + + // Update last mouse position + this.renderer.lastDragPoint = [event.clientX, event.clientY]; + + // Update offset + this.renderer.offsetX += deltaX; + this.renderer.offsetY += deltaY; + + // Calculate velocity based on the change in position and time + if (timeDiff > 0) { + this.renderer.panInertiaVelocity = [ + (deltaX / timeDiff) * 15, + (deltaY / timeDiff) * 15, + ]; + // console.log("velo", this.renderer.panInertiaVelocity) } - /** - * Handles mouse wheel events for zooming - */ - public handleWheel(event: WheelEvent): void { - event.preventDefault() - - // Calculate zoom center (mouse position) - const rect = this.renderer.canvas.getBoundingClientRect() - const mouseX = event.clientX - rect.left - const mouseY = event.clientY - rect.top - - // Calculate world coordinates before zoom - const worldXBefore = (mouseX - this.renderer.offsetX) / this.renderer.zoom - const worldYBefore = (mouseY - this.renderer.offsetY) / this.renderer.zoom - - // Update zoom level - const zoomDelta = event.deltaY > 0 ? 0.9 : 1.1 - this.renderer.zoomInertiaVelocity = (zoomDelta - 1) * 0.1 // Adjust multiplier for desired speed - - const newZoom = Math.max( - this.renderer.options.minZoom, - Math.min(this.renderer.options.maxZoom, this.renderer.zoom * zoomDelta), - ) - - // Calculate world coordinates after zoom - const worldXAfter = (mouseX - this.renderer.offsetX) / newZoom - const worldYAfter = (mouseY - this.renderer.offsetY) / newZoom - - // Adjust offset to keep the point under the mouse fixed - this.renderer.offsetX += (worldXAfter - worldXBefore) * newZoom - this.renderer.offsetY += (worldYAfter - worldYBefore) * newZoom - - this.renderer.updateZoom(newZoom) - this.renderer.updateCenter() - this.renderer.updateBounds() - - // this.requestRender() - this.renderer.needRender = true - this.renderer.effects.startZoomInertia() + // Render with new offset + // this.requestRender() + this.renderer.needRender = true; + + this.renderer.updateCenter(); + this.renderer.updateBounds(); + event.preventDefault(); + } + + /** + * Handles mouse up events for panning + */ + public handleMouseUp(event: MouseEvent): void { + let totalDistance = 0; + const timeDiff = Date.now() - this.renderer.dragStartTime; + + if (this.renderer.dragStartCoord !== null) { + const startPos = this.renderer.dragStartCoord; + const endPos: Coordinate = [event.clientX, event.clientY]; + totalDistance = Math.sqrt( + (endPos[0] - startPos[0]) ** 2 + (endPos[1] - startPos[1]) ** 2, + ); + + const deltaX = event.clientX - this.renderer.lastDragPoint[0]; + const deltaY = event.clientY - this.renderer.lastDragPoint[1]; + + // Update last mouse position + this.renderer.lastDragPoint = [event.clientX, event.clientY]; + + // Update offset + this.renderer.offsetX += deltaX; + this.renderer.offsetY += deltaY; + + // Start inertia + this.renderer.effects.startPanInertia(); } - /** - * Handles mouse down events for panning - */ - public handleMouseDown(event: MouseEvent): void { - event.preventDefault() - this.renderer.effects.stopPanInertia() - - this.renderer.dragStartTime = Date.now() - this.renderer.dragStartCoord = [event.clientX, event.clientY] - this.renderer.lastDragPoint = [event.clientX, event.clientY] - - // this.renderer.canvas.style.cursor = "grabbing" + if (timeDiff > 10 && timeDiff < 1500 && totalDistance < 10) { + // Click! + const rect = this.renderer.canvas.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + const x = Math.floor( + (mouseX - this.renderer.offsetX) / + (this.renderer.zoom * this.renderer.options.cellSize), + ); + const y = Math.floor( + (mouseY - this.renderer.offsetY) / + (this.renderer.zoom * this.renderer.options.cellSize), + ); + + this.renderer.core.events.emit("cellClicked", [x, y]); } - /** - * Handles mouse move events for panning - */ - public handleMouseMove(event: MouseEvent): void { - // TODO when doing hover effects, this has to be changed - if (!this.renderer.dragStartTime) return - - const currentTime = Date.now() - const timeDiff = currentTime - this.renderer.lastDragTime - this.renderer.lastDragTime = currentTime - - // Calculate the distance moved - const deltaX = event.clientX - this.renderer.lastDragPoint[0] - const deltaY = event.clientY - this.renderer.lastDragPoint[1] - - // Update last mouse position - this.renderer.lastDragPoint = [event.clientX, event.clientY] - - // Update offset - this.renderer.offsetX += deltaX - this.renderer.offsetY += deltaY - - // Calculate velocity based on the change in position and time - if (timeDiff > 0) { - this.renderer.panInertiaVelocity = [(deltaX / timeDiff) * 15, (deltaY / timeDiff) * 15] - // console.log("velo", this.renderer.panInertiaVelocity) - } - - // Render with new offset - // this.requestRender() - this.renderer.needRender = true - - this.renderer.updateCenter() - this.renderer.updateBounds() - event.preventDefault() + this.renderer.dragStartTime = 0; + this.renderer.dragStartCoord = null; + } + + /** + * Handles touch start events for panning on mobile + */ + public handleTouchStart(event: TouchEvent): void { + if (event.touches.length === 1) { + event.preventDefault(); + this.renderer.dragStartTime = Date.now(); + this.renderer.dragStartCoord = [ + event.touches[0].clientX, + event.touches[0].clientY, + ]; + this.renderer.lastDragPoint = [ + event.touches[0].clientX, + event.touches[0].clientY, + ]; + } else if (event.touches.length === 2) { + event.preventDefault(); + const [touch1, touch2] = Array.from(event.touches); + this.renderer.initialPinchDistance = Math.hypot( + touch2.clientX - touch1.clientX, + touch2.clientY - touch1.clientY, + ); + this.renderer.initialPinchZoom = this.renderer.zoom; } - - /** - * Handles mouse up events for panning - */ - public handleMouseUp(event: MouseEvent): void { - let totalDistance = 0 - const timeDiff = Date.now() - this.renderer.dragStartTime - - if (this.renderer.dragStartCoord !== null) { - const startPos = this.renderer.dragStartCoord - const endPos: Coordinate = [event.clientX, event.clientY] - totalDistance = Math.sqrt((endPos[0] - startPos[0]) ** 2 + (endPos[1] - startPos[1]) ** 2) - - const deltaX = event.clientX - this.renderer.lastDragPoint[0] - const deltaY = event.clientY - this.renderer.lastDragPoint[1] - - // Update last mouse position - this.renderer.lastDragPoint = [event.clientX, event.clientY] - - // Update offset - this.renderer.offsetX += deltaX - this.renderer.offsetY += deltaY - - // Start inertia - this.renderer.effects.startPanInertia() - } - - if (timeDiff > 10 && timeDiff < 1500 && totalDistance < 10) { - // Click! - const rect = this.renderer.canvas.getBoundingClientRect() - const mouseX = event.clientX - rect.left - const mouseY = event.clientY - rect.top - - const x = Math.floor( - (mouseX - this.renderer.offsetX) / (this.renderer.zoom * this.renderer.options.cellSize), - ) - const y = Math.floor( - (mouseY - this.renderer.offsetY) / (this.renderer.zoom * this.renderer.options.cellSize), - ) - - this.renderer.core.events.emit("cellClicked", [x, y]) - } - - this.renderer.dragStartTime = 0 - this.renderer.dragStartCoord = null + } + + /** + * Handles touch move events for panning on mobile + */ + public handleTouchMove(event: TouchEvent): void { + if (event.touches.length === 1 && this.renderer.dragStartTime) { + this.handleMouseMove(event.touches[0] as unknown as MouseEvent); + } else if ( + event.touches.length === 2 && + this.renderer.initialPinchDistance !== null + ) { + // Handle pinch move + const [touch1, touch2] = Array.from(event.touches); + + const newPinchDistance = Math.hypot( + touch2.clientX - touch1.clientX, + touch2.clientY - touch1.clientY, + ); + const pinchRatio = newPinchDistance / this.renderer.initialPinchDistance; + const newZoom = Math.max( + this.renderer.options.minZoom, + Math.min( + this.renderer.options.maxZoom, + this.renderer.initialPinchZoom * pinchRatio, + ), + ); + + // Calculate the midpoint between the two touch points + const rect = this.renderer.canvas.getBoundingClientRect(); + const midX = (touch1.clientX + touch2.clientX) / 2 - rect.left; + const midY = (touch1.clientY + touch2.clientY) / 2 - rect.top; + + // Adjust offset to keep the midpoint fixed + const worldXBefore = (midX - this.renderer.offsetX) / this.renderer.zoom; + const worldYBefore = (midY - this.renderer.offsetY) / this.renderer.zoom; + + const worldXAfter = (midX - this.renderer.offsetX) / newZoom; + const worldYAfter = (midY - this.renderer.offsetY) / newZoom; + + this.renderer.offsetX += (worldXAfter - worldXBefore) * newZoom; + this.renderer.offsetY += (worldYAfter - worldYBefore) * newZoom; + + // TODO fix + // Calculate zoom inertia velocity based on the change in zoom + this.renderer.zoomInertiaVelocity = + (newZoom / this.renderer.zoom - 1) * 0.1; // Adjust multiplier for desired speed + + this.renderer.updateZoom(newZoom); + this.renderer.updateCenter(); + this.renderer.updateBounds(); + // this.requestRender() + this.renderer.needRender = true; } - - /** - * Handles touch start events for panning on mobile - */ - public handleTouchStart(event: TouchEvent): void { - if (event.touches.length === 1) { - event.preventDefault() - this.renderer.dragStartTime = Date.now() - this.renderer.dragStartCoord = [event.touches[0].clientX, event.touches[0].clientY] - this.renderer.lastDragPoint = [event.touches[0].clientX, event.touches[0].clientY] - } else if (event.touches.length === 2) { - event.preventDefault() - const [touch1, touch2] = Array.from(event.touches) - this.renderer.initialPinchDistance = Math.hypot( - touch2.clientX - touch1.clientX, - touch2.clientY - touch1.clientY, - ) - this.renderer.initialPinchZoom = this.renderer.zoom - } - } - - /** - * Handles touch move events for panning on mobile - */ - public handleTouchMove(event: TouchEvent): void { - if (event.touches.length === 1 && this.renderer.dragStartTime) { - this.handleMouseMove(event.touches[0] as unknown as MouseEvent) - } else if (event.touches.length === 2 && this.renderer.initialPinchDistance !== null) { - // Handle pinch move - const [touch1, touch2] = Array.from(event.touches) - - const newPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY) - const pinchRatio = newPinchDistance / this.renderer.initialPinchDistance - const newZoom = Math.max( - this.renderer.options.minZoom, - Math.min(this.renderer.options.maxZoom, this.renderer.initialPinchZoom * pinchRatio), - ) - - // Calculate the midpoint between the two touch points - const rect = this.renderer.canvas.getBoundingClientRect() - const midX = (touch1.clientX + touch2.clientX) / 2 - rect.left - const midY = (touch1.clientY + touch2.clientY) / 2 - rect.top - - // Adjust offset to keep the midpoint fixed - const worldXBefore = (midX - this.renderer.offsetX) / this.renderer.zoom - const worldYBefore = (midY - this.renderer.offsetY) / this.renderer.zoom - - const worldXAfter = (midX - this.renderer.offsetX) / newZoom - const worldYAfter = (midY - this.renderer.offsetY) / newZoom - - this.renderer.offsetX += (worldXAfter - worldXBefore) * newZoom - this.renderer.offsetY += (worldYAfter - worldYBefore) * newZoom - - // TODO fix - // Calculate zoom inertia velocity based on the change in zoom - this.renderer.zoomInertiaVelocity = (newZoom / this.renderer.zoom - 1) * 0.1 // Adjust multiplier for desired speed - - this.renderer.updateZoom(newZoom) - this.renderer.updateCenter() - this.renderer.updateBounds() - // this.requestRender() - this.renderer.needRender = true - } - } - - /** - * Handles touch end events for panning on mobile - */ - public handleTouchEnd(event: TouchEvent): void { - if (event.touches.length === 0) { - // Reset pinch-related variables when all touches are lifted - // TODO - // this.renderer.initialPinchDistance = null - // this.renderer.initialZoom = this.renderer.zoom - const mouseEvent = { clientX: this.renderer.lastDragPoint[0], clientY: this.renderer.lastDragPoint[1] } - this.handleMouseUp(mouseEvent as unknown as MouseEvent) - } else if (event.touches.length === 1) { - // Still one touch remaining after pinching - this.renderer.dragStartTime = Date.now() - this.renderer.dragStartCoord = [event.touches[0].clientX, event.touches[0].clientY] - this.renderer.lastDragPoint = [event.touches[0].clientX, event.touches[0].clientY] - - this.renderer.effects.startZoomInertia() - // Handle as a mouse up event if there's still one touch remaining - } + } + + /** + * Handles touch end events for panning on mobile + */ + public handleTouchEnd(event: TouchEvent): void { + if (event.touches.length === 0) { + // Reset pinch-related variables when all touches are lifted + // TODO + // this.renderer.initialPinchDistance = null + // this.renderer.initialZoom = this.renderer.zoom + const mouseEvent = { + clientX: this.renderer.lastDragPoint[0], + clientY: this.renderer.lastDragPoint[1], + }; + this.handleMouseUp(mouseEvent as unknown as MouseEvent); + } else if (event.touches.length === 1) { + // Still one touch remaining after pinching + this.renderer.dragStartTime = Date.now(); + this.renderer.dragStartCoord = [ + event.touches[0].clientX, + event.touches[0].clientY, + ]; + this.renderer.lastDragPoint = [ + event.touches[0].clientX, + event.touches[0].clientY, + ]; + + this.renderer.effects.startZoomInertia(); + // Handle as a mouse up event if there's still one touch remaining } + } } diff --git a/packages/core/src/renderers/Canvas2DRenderer/index.ts b/packages/core/src/renderers/Canvas2DRenderer/index.ts index e72e585..6c8f038 100644 --- a/packages/core/src/renderers/Canvas2DRenderer/index.ts +++ b/packages/core/src/renderers/Canvas2DRenderer/index.ts @@ -1,273 +1,319 @@ -import type { Coordinate, Bounds, Pixel, Notification } from "../../types" -import { areBoundsEqual, areCoordinatesEqual, hexToRgb, numRGBAToHex } from "../../utils.ts" -import type { PixelawCore } from "../../PixelawCore.ts" - -import { type Canvas2DRendererOptions, DEFAULT_OPTIONS, type GlowingCell, isBrowser } from "./types" -import { getZoomLevel } from "./utils" -import { EventHandlers } from "./handlers" -import { RenderingMethods } from "./rendering" -import { EffectsManager } from "./effects" +import type { Coordinate, Bounds, Pixel, Notification } from "../../types"; +import { + areBoundsEqual, + areCoordinatesEqual, + hexToRgb, + numRGBAToHex, +} from "../../utils.ts"; +import type { PixelawCore } from "../../PixelawCore.ts"; + +import { + type Canvas2DRendererOptions, + DEFAULT_OPTIONS, + type GlowingCell, + isBrowser, +} from "./types"; +import { getZoomLevel } from "./utils"; +import { EventHandlers } from "./handlers"; +import { RenderingMethods } from "./rendering"; +import { EffectsManager } from "./effects"; /** * Manages the rendering of a grid of cells on a canvas */ export default class Canvas2DRenderer { - // Public access to canvas and context for handlers - public canvas: HTMLCanvasElement - public ctx: CanvasRenderingContext2D - public core: PixelawCore - public options: Canvas2DRendererOptions - - // Viewport state - public zoom: number - public center: Coordinate = [0, 0] - public bounds: Bounds = [ - [0, 0], - [0, 0], - ] - - // Public properties for handlers - public offsetX = 0 - public offsetY = 0 - public dragStartTime = 0 - public lastDragTime = 0 - - public dragStartCoord: Coordinate | null = null - public lastDragPoint: Coordinate = [0, 0] - public initialPinchDistance: number | null = null - public initialPinchZoom: number - - // For panning "inertia" - public panInertiaVelocity: [number, number] = [0, 0] - public isPanInertiaActive = false - - // For zooming "inertia" - public zoomInertiaVelocity: number - public isZoomInertiaActive = false - - // Glow - public glowingCells: Map = new Map() - public glowInterval: NodeJS.Timeout | null = null - - // Notification - public activeNotifications: Map = new Map() - - public readonly decelerationFactor = 0.9 // Adjust for desired deceleration - - // Component instances - private handlers: EventHandlers - private rendering: RenderingMethods - effects: EffectsManager - - // render management - public needRender = true - - constructor(core: PixelawCore, options: Partial = {}) { - // Create a new canvas element - this.canvas = document.createElement("canvas") - this.canvas.style.width = "100%" - this.canvas.style.height = "100%" - this.canvas.style.display = "block" - this.canvas.style.cursor = "grab" - - // Get canvas context - const ctx = this.canvas.getContext("2d") - if (!ctx) { - throw new Error("Failed to get canvas 2D context") - } - this.ctx = ctx - - // Initialize properties - this.core = core - this.options = { ...DEFAULT_OPTIONS, ...options } - this.zoom = core.zoom - - // Initialize component instances - this.handlers = new EventHandlers(this) - this.rendering = new RenderingMethods(this) - this.effects = new EffectsManager(this) - - // Initialize event listeners - this.handlers.initEventListeners() - - // Set initial canvas size when it's attached to the DOM - const resizeObserver = new ResizeObserver(() => { - this.resizeCanvas() - }) - resizeObserver.observe(this.canvas) - - this.subscribeToEvents() - - this.setCenter(core.center) - - this.startRenderLoop() - } - - private subscribeToEvents() { - if (!this.core.pixelStore) throw new Error("PixelStore not initialized") - // TODO decide on whether pixelstore cacheupdated goes to a global event bus or not - this.core.pixelStore.eventEmitter.on("cacheUpdated", (_timestamp: number) => { - console.log("cacheUpdated") - this.needRender = true - // this.requestRender() - }) - - this.core.events.on("pixelStoreUpdated", (timestamp: number) => { - console.log(`pixelStoreUpdated at: ${timestamp}`) - // this.requestRender() - this.needRender = true - }) - this.core.events.on("tileStoreUpdated", (timestamp: number) => { - console.log(`tileStoreUpdated at: ${timestamp}`) - // this.requestRender() - this.needRender = true - }) - } - - public startRenderLoop() { - const renderLoop = () => { - if (this.needRender) { - this.needRender = false - this.requestRender() - } - requestAnimationFrame(renderLoop) - } - requestAnimationFrame(renderLoop) - } - - public requestRender() { - if (isBrowser) { - requestAnimationFrame(() => { - this.rendering.render() - }) - } else { - setImmediate(() => { - this.rendering.render() - }) - } - } - - public setContainer(container: HTMLElement) { - this.canvas.width = container.clientWidth - this.canvas.height = container.clientHeight - container.appendChild(this.canvas) - } - - /** - * Resizes the canvas to fill its container - */ - private resizeCanvas(): void { - const container = this.canvas.parentElement - - if (container) { - this.canvas.width = container.clientWidth - this.canvas.height = container.clientHeight - // this.requestRender() - this.needRender = true - - this.updateBounds() - this.setCenter(this.center) - } - } - - /** - * Starts a glow effect on a specific cell by delegating to the effects manager - */ - public addGlow(coordinate: Coordinate, duration: number, htmlColor: string, intensity: number, size: number): void { - this.effects.addGlow(coordinate, duration, htmlColor, intensity, size) + // Public access to canvas and context for handlers + public canvas: HTMLCanvasElement; + public ctx: CanvasRenderingContext2D; + public core: PixelawCore; + public options: Canvas2DRendererOptions; + + // Viewport state + public zoom: number; + public center: Coordinate = [0, 0]; + public bounds: Bounds = [ + [0, 0], + [0, 0], + ]; + + // Public properties for handlers + public offsetX = 0; + public offsetY = 0; + public dragStartTime = 0; + public lastDragTime = 0; + + public dragStartCoord: Coordinate | null = null; + public lastDragPoint: Coordinate = [0, 0]; + public initialPinchDistance: number | null = null; + public initialPinchZoom: number; + + // For panning "inertia" + public panInertiaVelocity: [number, number] = [0, 0]; + public isPanInertiaActive = false; + + // For zooming "inertia" + public zoomInertiaVelocity: number; + public isZoomInertiaActive = false; + + // Glow + public glowingCells: Map = new Map(); + public glowInterval: NodeJS.Timeout | null = null; + + // Notification + public activeNotifications: Map = new Map(); + + public readonly decelerationFactor = 0.9; // Adjust for desired deceleration + + // Component instances + private handlers: EventHandlers; + private rendering: RenderingMethods; + effects: EffectsManager; + + // render management + public needRender = true; + + constructor( + core: PixelawCore, + options: Partial = {}, + ) { + // Create a new canvas element + this.canvas = document.createElement("canvas"); + this.canvas.style.width = "100%"; + this.canvas.style.height = "100%"; + this.canvas.style.display = "block"; + this.canvas.style.cursor = "grab"; + + // Get canvas context + const ctx = this.canvas.getContext("2d"); + if (!ctx) { + throw new Error("Failed to get canvas 2D context"); } - - /** - * Starts a glow effect on a specific cell by delegating to the effects manager - */ - public addNotification(coordinate: Coordinate, duration: number, message: string): void { - this.effects.addNotification(coordinate, duration, message) + this.ctx = ctx; + + // Initialize properties + this.core = core; + this.options = { ...DEFAULT_OPTIONS, ...options }; + this.zoom = core.zoom; + + // Initialize component instances + this.handlers = new EventHandlers(this); + this.rendering = new RenderingMethods(this); + this.effects = new EffectsManager(this); + + // Initialize event listeners + this.handlers.initEventListeners(); + + // Set initial canvas size when it's attached to the DOM + const resizeObserver = new ResizeObserver(() => { + this.resizeCanvas(); + }); + resizeObserver.observe(this.canvas); + + this.subscribeToEvents(); + + this.setCenter(core.center); + + this.startRenderLoop(); + } + + private subscribeToEvents() { + if (!this.core.pixelStore) throw new Error("PixelStore not initialized"); + // TODO decide on whether pixelstore cacheupdated goes to a global event bus or not + this.core.pixelStore.eventEmitter.on( + "cacheUpdated", + (_timestamp: number) => { + console.log("cacheUpdated"); + this.needRender = true; + // this.requestRender() + }, + ); + + this.core.events.on("pixelStoreUpdated", (timestamp: number) => { + console.log(`pixelStoreUpdated at: ${timestamp}`); + // this.requestRender() + this.needRender = true; + }); + this.core.events.on("tileStoreUpdated", (timestamp: number) => { + console.log(`tileStoreUpdated at: ${timestamp}`); + // this.requestRender() + this.needRender = true; + }); + } + + public startRenderLoop() { + const renderLoop = () => { + if (this.needRender) { + this.needRender = false; + this.requestRender(); + } + requestAnimationFrame(renderLoop); + }; + requestAnimationFrame(renderLoop); + } + + public requestRender() { + if (isBrowser) { + requestAnimationFrame(() => { + this.rendering.render(); + }); + } else { + setImmediate(() => { + this.rendering.render(); + }); } - - /** - * Converts screen coordinates to grid coordinate as a Coordinate type - */ - private screenToGridCoordinate(screenX: number, screenY: number): Coordinate { - const gridX = Math.floor((screenX - this.offsetX) / (this.zoom * this.options.cellSize)) - const gridY = Math.floor((screenY - this.offsetY) / (this.zoom * this.options.cellSize)) - return [gridX, gridY] + } + + public setContainer(container: HTMLElement) { + this.canvas.width = container.clientWidth; + this.canvas.height = container.clientHeight; + container.appendChild(this.canvas); + } + + /** + * Resizes the canvas to fill its container + */ + private resizeCanvas(): void { + const container = this.canvas.parentElement; + + if (container) { + this.canvas.width = container.clientWidth; + this.canvas.height = container.clientHeight; + // this.requestRender() + this.needRender = true; + + this.updateBounds(); + this.setCenter(this.center); } - - /** - * Gets the current zoom level - */ - public getZoom(): number { - return this.zoom + } + + /** + * Starts a glow effect on a specific cell by delegating to the effects manager + */ + public addGlow( + coordinate: Coordinate, + duration: number, + htmlColor: string, + intensity: number, + size: number, + ): void { + this.effects.addGlow(coordinate, duration, htmlColor, intensity, size); + } + + /** + * Starts a glow effect on a specific cell by delegating to the effects manager + */ + public addNotification( + coordinate: Coordinate, + duration: number, + message: string, + ): void { + this.effects.addNotification(coordinate, duration, message); + } + + /** + * Converts screen coordinates to grid coordinate as a Coordinate type + */ + private screenToGridCoordinate(screenX: number, screenY: number): Coordinate { + const gridX = Math.floor( + (screenX - this.offsetX) / (this.zoom * this.options.cellSize), + ); + const gridY = Math.floor( + (screenY - this.offsetY) / (this.zoom * this.options.cellSize), + ); + return [gridX, gridY]; + } + + /** + * Gets the current zoom level + */ + public getZoom(): number { + return this.zoom; + } + + /** + * Gets the current visible bounds + */ + public getBounds(): Bounds { + const topLeft = this.screenToGridCoordinate(0, 0); + const bottomRight = this.screenToGridCoordinate( + this.canvas.width, + this.canvas.height, + ); + return [topLeft, bottomRight]; + } + + /** + * Sets the zoom level + */ + public setZoom(zoom: number): void { + if (this.zoom !== zoom) { + // Set zoom level first + this.zoom = Math.max( + this.options.minZoom, + Math.min(this.options.maxZoom, zoom), + ); + + this.core.events.emit("zoomChanged", this.zoom); } - - /** - * Gets the current visible bounds - */ - public getBounds(): Bounds { - const topLeft = this.screenToGridCoordinate(0, 0) - const bottomRight = this.screenToGridCoordinate(this.canvas.width, this.canvas.height) - return [topLeft, bottomRight] + } + + /** + * Gets the center position in grid coordinates + */ + public getCenter(): Coordinate { + const centerX = Math.floor( + (this.canvas.width / 2 - this.offsetX) / + (this.zoom * this.options.cellSize), + ); + const centerY = Math.floor( + (this.canvas.height / 2 - this.offsetY) / + (this.zoom * this.options.cellSize), + ); + return [centerX, centerY]; + } + + /** + * Centers the viewport on the specified coordinate + */ + public setCenter(newCenter: Coordinate): void { + if (this.getCenter() !== newCenter) { + // Then calculate the offset needed to center on the specified coordinates + this.offsetX = + this.canvas.width / 2 - + newCenter[0] * this.options.cellSize * this.zoom; + this.offsetY = + this.canvas.height / 2 - + newCenter[1] * this.options.cellSize * this.zoom; + + this.updateCenter(); + this.updateBounds(); } - - /** - * Sets the zoom level - */ - public setZoom(zoom: number): void { - if (this.zoom !== zoom) { - // Set zoom level first - this.zoom = Math.max(this.options.minZoom, Math.min(this.options.maxZoom, zoom)) - - this.core.events.emit("zoomChanged", this.zoom) - } + } + + // Update this.center and emit event, if applicable + public updateCenter(): void { + const currentCenter = this.center; + const newCenter = this.getCenter(); + if (!areCoordinatesEqual(currentCenter, newCenter)) { + this.center = newCenter; + this.core.events.emit("centerChanged", newCenter); } + } - /** - * Gets the center position in grid coordinates - */ - public getCenter(): Coordinate { - const centerX = Math.floor((this.canvas.width / 2 - this.offsetX) / (this.zoom * this.options.cellSize)) - const centerY = Math.floor((this.canvas.height / 2 - this.offsetY) / (this.zoom * this.options.cellSize)) - return [centerX, centerY] + public updateZoom(newZoom: number): void { + const currentZoom = this.zoom; + if (currentZoom !== newZoom) { + this.zoom = newZoom; + this.core.events.emit("zoomChanged", newZoom); } - - /** - * Centers the viewport on the specified coordinate - */ - public setCenter(newCenter: Coordinate): void { - if (this.getCenter() !== newCenter) { - // Then calculate the offset needed to center on the specified coordinates - this.offsetX = this.canvas.width / 2 - newCenter[0] * this.options.cellSize * this.zoom - this.offsetY = this.canvas.height / 2 - newCenter[1] * this.options.cellSize * this.zoom - - this.updateCenter() - this.updateBounds() - } - } - - // Update this.center and emit event, if applicable - public updateCenter(): void { - const currentCenter = this.center - const newCenter = this.getCenter() - if (!areCoordinatesEqual(currentCenter, newCenter)) { - this.center = newCenter - this.core.events.emit("centerChanged", newCenter) - } - } - - public updateZoom(newZoom: number): void { - const currentZoom = this.zoom - if (currentZoom !== newZoom) { - this.zoom = newZoom - this.core.events.emit("zoomChanged", newZoom) - } - } - - public updateBounds(): void { - const currentBounds = this.bounds - const newBounds = this.getBounds() - if (!areBoundsEqual(currentBounds, newBounds)) { - this.bounds = newBounds - this.core.events.emit("boundsChanged", newBounds) - } + } + + public updateBounds(): void { + const currentBounds = this.bounds; + const newBounds = this.getBounds(); + if (!areBoundsEqual(currentBounds, newBounds)) { + this.bounds = newBounds; + this.core.events.emit("boundsChanged", newBounds); } + } } diff --git a/packages/core/src/renderers/Canvas2DRenderer/rendering.ts b/packages/core/src/renderers/Canvas2DRenderer/rendering.ts index 7844c2a..488dc1e 100644 --- a/packages/core/src/renderers/Canvas2DRenderer/rendering.ts +++ b/packages/core/src/renderers/Canvas2DRenderer/rendering.ts @@ -1,185 +1,230 @@ -import { type Bounds, MAX_DIMENSION, type Pixel } from "../../types" -import { numRGBAToHex } from "../../utils" -import { getZoomLevel } from "./utils" -import type Canvas2DRenderer from "./index.ts" +import { type Bounds, MAX_DIMENSION, type Pixel } from "../../types"; +import { numRGBAToHex } from "../../utils"; +import { getZoomLevel } from "./utils"; +import type Canvas2DRenderer from "./index.ts"; /** * Rendering methods for Canvas2DRenderer * This file contains all the methods related to rendering the grid and cells */ export class RenderingMethods { - private renderer: Canvas2DRenderer - - constructor(renderer: Canvas2DRenderer) { - this.renderer = renderer + private renderer: Canvas2DRenderer; + + constructor(renderer: Canvas2DRenderer) { + this.renderer = renderer; + } + + /** + * Renders the grid and cells + */ + public render(): void { + // Clear canvas + this.renderer.ctx.fillStyle = this.renderer.options.backgroundColor; + this.renderer.ctx.fillRect( + 0, + 0, + this.renderer.canvas.width, + this.renderer.canvas.height, + ); + + this.renderer.effects.handlePanInertia(); + this.renderer.effects.handleZoomInertia(); + + // Draw visible cells + this.drawCells(); + + // Draw grid lines if enabled + if ( + this.renderer.options.showGridLines && + getZoomLevel(this.renderer.zoom) !== "far" + ) { + this.drawGridLines(); } - /** - * Renders the grid and cells - */ - public render(): void { - // Clear canvas - this.renderer.ctx.fillStyle = this.renderer.options.backgroundColor - this.renderer.ctx.fillRect(0, 0, this.renderer.canvas.width, this.renderer.canvas.height) - - this.renderer.effects.handlePanInertia() - this.renderer.effects.handleZoomInertia() - - // Draw visible cells - this.drawCells() - - // Draw grid lines if enabled - if (this.renderer.options.showGridLines && getZoomLevel(this.renderer.zoom) !== "far") { - this.drawGridLines() - } - - // Draw glowing cells last to ensure glow is on top - this.drawGlowingCells() - - this.drawNotifications() + // Draw glowing cells last to ensure glow is on top + this.drawGlowingCells(); + + this.drawNotifications(); + } + + private getRenderableBounds(): Bounds { + let [[left, top], [right, bottom]] = this.renderer.getBounds(); + + left = Math.max(left, 0); + top = Math.max(top, 0); + right = Math.min(MAX_DIMENSION, right); + bottom = Math.min(MAX_DIMENSION, bottom); + + return [ + [left, top], + [right, bottom], + ]; + } + + /** + * Draws grid lines + */ + private drawGridLines(): void { + const [[left, top], [right, bottom]] = this.getRenderableBounds(); + + const { cellSize } = this.renderer.options; + + this.renderer.ctx.strokeStyle = this.renderer.options.gridLineColor; + this.renderer.ctx.lineWidth = 1; + + // Draw vertical grid lines + for (let x = left; x <= right; x++) { + const screenX = + Math.floor(x * cellSize * this.renderer.zoom + this.renderer.offsetX) + + 0.5; + this.renderer.ctx.beginPath(); + this.renderer.ctx.moveTo( + screenX, + top * cellSize * this.renderer.zoom + this.renderer.offsetY, + ); + this.renderer.ctx.lineTo( + screenX, + (bottom + 1) * cellSize * this.renderer.zoom + this.renderer.offsetY, + ); + this.renderer.ctx.stroke(); } - private getRenderableBounds(): Bounds { - let [[left, top], [right, bottom]] = this.renderer.getBounds() - - left = Math.max(left, 0) - top = Math.max(top, 0) - right = Math.min(MAX_DIMENSION, right) - bottom = Math.min(MAX_DIMENSION, bottom) - - return [ - [left, top], - [right, bottom], - ] - } - - /** - * Draws grid lines - */ - private drawGridLines(): void { - const [[left, top], [right, bottom]] = this.getRenderableBounds() - - const { cellSize } = this.renderer.options - - this.renderer.ctx.strokeStyle = this.renderer.options.gridLineColor - this.renderer.ctx.lineWidth = 1 - - // Draw vertical grid lines - for (let x = left; x <= right; x++) { - const screenX = Math.floor(x * cellSize * this.renderer.zoom + this.renderer.offsetX) + 0.5 - this.renderer.ctx.beginPath() - this.renderer.ctx.moveTo(screenX, top * cellSize * this.renderer.zoom + this.renderer.offsetY) - this.renderer.ctx.lineTo(screenX, (bottom + 1) * cellSize * this.renderer.zoom + this.renderer.offsetY) - this.renderer.ctx.stroke() - } - - // Draw horizontal grid lines - for (let y = top; y <= bottom; y++) { - const screenY = Math.floor(y * cellSize * this.renderer.zoom + this.renderer.offsetY) + 0.5 - this.renderer.ctx.beginPath() - this.renderer.ctx.moveTo(left * cellSize * this.renderer.zoom + this.renderer.offsetX, screenY) - this.renderer.ctx.lineTo((right + 1) * cellSize * this.renderer.zoom + this.renderer.offsetX, screenY) - this.renderer.ctx.stroke() - } + // Draw horizontal grid lines + for (let y = top; y <= bottom; y++) { + const screenY = + Math.floor(y * cellSize * this.renderer.zoom + this.renderer.offsetY) + + 0.5; + this.renderer.ctx.beginPath(); + this.renderer.ctx.moveTo( + left * cellSize * this.renderer.zoom + this.renderer.offsetX, + screenY, + ); + this.renderer.ctx.lineTo( + (right + 1) * cellSize * this.renderer.zoom + this.renderer.offsetX, + screenY, + ); + this.renderer.ctx.stroke(); } + } - private drawGlowingCells(): void { - for (const [key, glow] of this.renderer.glowingCells.entries()) { - const [x, y] = key.split(",").map(Number) - const pixel = this.renderer.core.pixelStore.getPixel([x, y]) + private drawGlowingCells(): void { + for (const [key, glow] of this.renderer.glowingCells.entries()) { + const [x, y] = key.split(",").map(Number); + const pixel = this.renderer.core.pixelStore.getPixel([x, y]); - if (!pixel) continue + if (!pixel) continue; - const { cellSize } = this.renderer.options - const screenX = x * cellSize * this.renderer.zoom + this.renderer.offsetX - const screenY = y * cellSize * this.renderer.zoom + this.renderer.offsetY - const screenSize = cellSize * this.renderer.zoom + const { cellSize } = this.renderer.options; + const screenX = x * cellSize * this.renderer.zoom + this.renderer.offsetX; + const screenY = y * cellSize * this.renderer.zoom + this.renderer.offsetY; + const screenSize = cellSize * this.renderer.zoom; - // Apply glow effect - this.renderer.ctx.shadowBlur = glow.size * glow.intensity - this.renderer.ctx.shadowColor = `rgba(${glow.color[0]}, ${glow.color[1]}, ${glow.color[2]}, ${glow.intensity})` + // Apply glow effect + this.renderer.ctx.shadowBlur = glow.size * glow.intensity; + this.renderer.ctx.shadowColor = `rgba(${glow.color[0]}, ${glow.color[1]}, ${glow.color[2]}, ${glow.intensity})`; - // Draw cell with glow - this.renderer.ctx.fillStyle = numRGBAToHex(pixel.color as number) - this.renderer.ctx.fillRect(screenX, screenY, screenSize, screenSize) + // Draw cell with glow + this.renderer.ctx.fillStyle = numRGBAToHex(pixel.color as number); + this.renderer.ctx.fillRect(screenX, screenY, screenSize, screenSize); - // Reset shadow settings - this.renderer.ctx.shadowBlur = 0 + // Reset shadow settings + this.renderer.ctx.shadowBlur = 0; - // Draw the inside of the cell again - this.drawCell(pixel) - } + // Draw the inside of the cell again + this.drawCell(pixel); } - - public drawNotifications(): void { - for (const [key, notification] of this.renderer.activeNotifications.entries()) { - const [x, y] = key.split(",").map(Number) - const screenX = x * this.renderer.options.cellSize * this.renderer.zoom + this.renderer.offsetX - const screenY = y * this.renderer.options.cellSize * this.renderer.zoom + this.renderer.offsetY - - this.renderer.ctx.fillStyle = "rgba(255, 255, 255, 0.8)" - this.renderer.ctx.fillRect(screenX, screenY - 20, 100, 20) - this.renderer.ctx.fillStyle = "black" - this.renderer.ctx.fillText(notification.text, screenX + 5, screenY - 5) - } + } + + public drawNotifications(): void { + for (const [ + key, + notification, + ] of this.renderer.activeNotifications.entries()) { + const [x, y] = key.split(",").map(Number); + const screenX = + x * this.renderer.options.cellSize * this.renderer.zoom + + this.renderer.offsetX; + const screenY = + y * this.renderer.options.cellSize * this.renderer.zoom + + this.renderer.offsetY; + + this.renderer.ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; + this.renderer.ctx.fillRect(screenX, screenY - 20, 100, 20); + this.renderer.ctx.fillStyle = "black"; + this.renderer.ctx.fillText(notification.text, screenX + 5, screenY - 5); } - - /** - * Draws cells in the visible region using Bounds - */ - private drawCells(): void { - const [[left, top], [right, bottom]] = this.getRenderableBounds() - - // Get all cells in the region - for (let x = left; x <= right; x++) { - for (let y = top; y <= bottom; y++) { - const pixel = this.renderer.core.pixelStore.getPixel([x, y]) - - if (!pixel) continue - this.drawCell(pixel) - } - } + } + + /** + * Draws cells in the visible region using Bounds + */ + private drawCells(): void { + const [[left, top], [right, bottom]] = this.getRenderableBounds(); + + // Get all cells in the region + for (let x = left; x <= right; x++) { + for (let y = top; y <= bottom; y++) { + const pixel = this.renderer.core.pixelStore.getPixel([x, y]); + + if (!pixel) continue; + this.drawCell(pixel); + } } - - /** - * Draws a single cell - */ - private drawCell(pixel: Pixel): void { - const { cellSize } = this.renderer.options - - // Calculate screen coordinates - const screenX = pixel.x * cellSize * this.renderer.zoom + this.renderer.offsetX - const screenY = pixel.y * cellSize * this.renderer.zoom + this.renderer.offsetY - const screenSize = cellSize * this.renderer.zoom - - // Draw cell background - this.renderer.ctx.fillStyle = numRGBAToHex(pixel.color as number) - this.renderer.ctx.fillRect(screenX, screenY, screenSize, screenSize) - - const zoomlevel = getZoomLevel(this.renderer.zoom) - if (zoomlevel === "close") { - // The icon - const fontWeight = "300" - this.renderer.ctx.font = `${fontWeight} ${screenSize * 0.2}px "Noto Emoji", serif` - this.renderer.ctx.textAlign = "center" - this.renderer.ctx.textBaseline = "middle" - this.renderer.ctx.fillStyle = "#000000" - this.renderer.ctx.fillText(pixel.text, screenX + screenSize / 2, screenY + screenSize * 0.55) - - // The app name - this.renderer.ctx.font = `${fontWeight} ${screenSize * 0.2}px Arial, sans-serif` - this.renderer.ctx.textAlign = "center" - this.renderer.ctx.textBaseline = "middle" - this.renderer.ctx.fillStyle = "#000000" - this.renderer.ctx.fillText(pixel.app, screenX + screenSize / 2, screenY + screenSize * 0.1) - } else if (zoomlevel === "mid") { - if (!pixel.text) return - const fontWeight = "300" - this.renderer.ctx.font = `${fontWeight} ${screenSize * 0.8}px "Arial", serif` - this.renderer.ctx.textAlign = "center" - this.renderer.ctx.textBaseline = "middle" - this.renderer.ctx.fillStyle = "#000000" - this.renderer.ctx.fillText(pixel.text, screenX + screenSize / 2, screenY + screenSize * 0.55) - } + } + + /** + * Draws a single cell + */ + private drawCell(pixel: Pixel): void { + const { cellSize } = this.renderer.options; + + // Calculate screen coordinates + const screenX = + pixel.x * cellSize * this.renderer.zoom + this.renderer.offsetX; + const screenY = + pixel.y * cellSize * this.renderer.zoom + this.renderer.offsetY; + const screenSize = cellSize * this.renderer.zoom; + + // Draw cell background + this.renderer.ctx.fillStyle = numRGBAToHex(pixel.color as number); + this.renderer.ctx.fillRect(screenX, screenY, screenSize, screenSize); + + const zoomlevel = getZoomLevel(this.renderer.zoom); + if (zoomlevel === "close") { + // The icon + const fontWeight = "300"; + this.renderer.ctx.font = `${fontWeight} ${screenSize * 0.2}px "Noto Emoji", serif`; + this.renderer.ctx.textAlign = "center"; + this.renderer.ctx.textBaseline = "middle"; + this.renderer.ctx.fillStyle = "#000000"; + this.renderer.ctx.fillText( + pixel.text, + screenX + screenSize / 2, + screenY + screenSize * 0.55, + ); + + // The app name + this.renderer.ctx.font = `${fontWeight} ${screenSize * 0.2}px Arial, sans-serif`; + this.renderer.ctx.textAlign = "center"; + this.renderer.ctx.textBaseline = "middle"; + this.renderer.ctx.fillStyle = "#000000"; + this.renderer.ctx.fillText( + pixel.app, + screenX + screenSize / 2, + screenY + screenSize * 0.1, + ); + } else if (zoomlevel === "mid") { + if (!pixel.text) return; + const fontWeight = "300"; + this.renderer.ctx.font = `${fontWeight} ${screenSize * 0.8}px "Arial", serif`; + this.renderer.ctx.textAlign = "center"; + this.renderer.ctx.textBaseline = "middle"; + this.renderer.ctx.fillStyle = "#000000"; + this.renderer.ctx.fillText( + pixel.text, + screenX + screenSize / 2, + screenY + screenSize * 0.55, + ); } + } } diff --git a/packages/core/src/renderers/Canvas2DRenderer/types.ts b/packages/core/src/renderers/Canvas2DRenderer/types.ts index d6f578b..82371c4 100644 --- a/packages/core/src/renderers/Canvas2DRenderer/types.ts +++ b/packages/core/src/renderers/Canvas2DRenderer/types.ts @@ -1,55 +1,56 @@ -import type { Bounds, Coordinate, Pixel, RGB } from "../../types" -import type { PixelawCore } from "../../PixelawCore.ts" -export * from "./index.ts" +import type { Bounds, Coordinate, Pixel, RGB } from "../../types"; +import type { PixelawCore } from "../../PixelawCore.ts"; +export * from "./index.ts"; -export { getZoomLevel } from "./utils.ts" +export { getZoomLevel } from "./utils.ts"; -export const ZOOM_MAX = 30 -export const ZOOM_CLOSE_MAX = 22 -export const ZOOM_MID_MAX = 4 -export const ZOOM_FAR_MAX = 0.25 -export const ZOOM_DEFAULT = 0.25 +export const ZOOM_MAX = 30; +export const ZOOM_CLOSE_MAX = 22; +export const ZOOM_MID_MAX = 4; +export const ZOOM_FAR_MAX = 0.25; +export const ZOOM_DEFAULT = 0.25; -export type ZOOM_LEVEL = "far" | "mid" | "close" +export type ZOOM_LEVEL = "far" | "mid" | "close"; export interface GlowingCell { - intensity: number - duration: number - startTime: number - color: RGB - size: number + intensity: number; + duration: number; + startTime: number; + color: RGB; + size: number; } interface Notification { - startTime: number - duration: number - message: string + startTime: number; + duration: number; + message: string; } /** * Configuration options for the grid renderer */ export interface Canvas2DRendererOptions { - cellSize: number - defaultZoom: number - minZoom: number - maxZoom: number - backgroundColor: string - gridLineColor: string - showGridLines: boolean + cellSize: number; + defaultZoom: number; + minZoom: number; + maxZoom: number; + backgroundColor: string; + gridLineColor: string; + showGridLines: boolean; } /** * Default configuration for the grid renderer */ export const DEFAULT_OPTIONS: Canvas2DRendererOptions = { - cellSize: 10, - defaultZoom: ZOOM_DEFAULT, - minZoom: ZOOM_FAR_MAX, - maxZoom: ZOOM_MAX, - backgroundColor: "#000000", - gridLineColor: "#444444", - showGridLines: true, -} - -export const isBrowser = typeof window !== "undefined" && typeof document !== "undefined" + cellSize: 10, + defaultZoom: ZOOM_DEFAULT, + minZoom: ZOOM_FAR_MAX, + maxZoom: ZOOM_MAX, + backgroundColor: "#000000", + gridLineColor: "#444444", + showGridLines: true, +}; + +export const isBrowser = + typeof window !== "undefined" && typeof document !== "undefined"; diff --git a/packages/core/src/renderers/Canvas2DRenderer/utils.ts b/packages/core/src/renderers/Canvas2DRenderer/utils.ts index 93d24ee..5a177e4 100644 --- a/packages/core/src/renderers/Canvas2DRenderer/utils.ts +++ b/packages/core/src/renderers/Canvas2DRenderer/utils.ts @@ -1,10 +1,10 @@ -import { type ZOOM_LEVEL, ZOOM_MID_MAX, ZOOM_CLOSE_MAX } from "./types" +import { type ZOOM_LEVEL, ZOOM_MID_MAX, ZOOM_CLOSE_MAX } from "./types"; /** * Determines the zoom level based on the current zoom value */ export function getZoomLevel(zoom: number): ZOOM_LEVEL { - if (zoom < ZOOM_MID_MAX) return "far" - if (zoom < ZOOM_CLOSE_MAX) return "mid" - return "close" + if (zoom < ZOOM_MID_MAX) return "far"; + if (zoom < ZOOM_CLOSE_MAX) return "mid"; + return "close"; } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ab4d17e..cd4017e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,270 +1,275 @@ -import type mitt from "mitt" -import type { PixelawCore } from "./PixelawCore.ts" +import type mitt from "mitt"; +import type { PixelawCore } from "./PixelawCore.ts"; export type Pixel = { - x: number - y: number - action: string - app: string - color: number | string - owner: string - text: string - timestamp: number | string -} + x: number; + y: number; + action: string; + app: string; + color: number | string; + owner: string; + text: string; + timestamp: number | string; +}; export type App = { - system: string - name: string - icon: string - action: string - plugin: string - entity: { - id: string - } -} + system: string; + name: string; + icon: string; + action: string; + plugin: string; + entity: { + id: string; + }; +}; export type Variant = { - name: string - value: number -} + name: string; + value: number; +}; -export type InteractParams = InteractParam[] +export type InteractParams = InteractParam[]; export type InteractParam = { - name: string - type: "string" | "number" | "enum" | "emoji" - typeName: string | null - variants: Variant[] - transformer: () => Promise - value?: number | string | null - systemOnly?: boolean -} + name: string; + type: "string" | "number" | "enum" | "emoji"; + typeName: string | null; + variants: Variant[]; + transformer: () => Promise; + value?: number | string | null; + systemOnly?: boolean; +}; -export type Tile = HTMLImageElement +export type Tile = HTMLImageElement; export interface UpdateService { - // tileChanged: TileChangedMessage | null - setBounds: (newBounds: Bounds) => void + // tileChanged: TileChangedMessage | null + setBounds: (newBounds: Bounds) => void; } export interface AppStore { - getByName: (name: string) => App | undefined - getBySystem: (system: string) => App | undefined - getAll: () => App[] + getByName: (name: string) => App | undefined; + getBySystem: (system: string) => App | undefined; + getAll: () => App[]; } // TODO this is rough and maybe dojo centric export type QueueItem = { - id: string - timestamp: number - called_system: string - selector: string - calldata: string -} + id: string; + timestamp: number; + called_system: string; + selector: string; + calldata: string; +}; export type Notification = { - position: Position - app: App - color: number - from: string | null - to: string | null - text: string -} + position: Position; + app: App; + color: number; + from: string | null; + to: string | null; + text: string; +}; export type NotificationStoreEvents = { - updated: number - added: Notification -} + updated: number; + added: Notification; +}; export type QueueStoreEvents = { - updated: number - scheduled: QueueItem -} + updated: number; + scheduled: QueueItem; +}; export interface QueueStore { - eventEmitter: ReturnType> - getAll: () => QueueItem[] - retrieve: () => Promise + eventEmitter: ReturnType>; + getAll: () => QueueItem[]; + retrieve: () => Promise; } export interface NotificationStore { - eventEmitter: ReturnType> - getAll: () => Notification[] - // getLastForPosition: (position: Position) => Notification[] + eventEmitter: ReturnType>; + getAll: () => Notification[]; + // getLastForPosition: (position: Position) => Notification[] } export interface TileStore { - refresh: () => void - prepare: (bounds: Bounds) => void - // fetchTile: (key: string) => void - // getTile: (key: string) => Tile | undefined | ""; - // setTile: (key: string, tile: Tile) => Promise; - setTiles: (tiles: { key: string; tile: Tile }[]) => Promise - tileset: Tileset | null - cacheUpdated: number + refresh: () => void; + prepare: (bounds: Bounds) => void; + // fetchTile: (key: string) => void + // getTile: (key: string) => Tile | undefined | ""; + // setTile: (key: string, tile: Tile) => Promise; + setTiles: (tiles: { key: string; tile: Tile }[]) => Promise; + tileset: Tileset | null; + cacheUpdated: number; } export interface Interaction { - // action: (params: InteractParams) => void - getUserParams: () => InteractParams - setUserParam: (name: string, value: unknown) => void - execute: () => Promise + // action: (params: InteractParams) => void + getUserParams: () => InteractParams; + setUserParam: (name: string, value: unknown) => void; + execute: () => Promise; } export interface Tileset { - tileSize: number - scaleFactor: number - bounds: Bounds - tileRows: (Tile | undefined | "")[][] + tileSize: number; + scaleFactor: number; + bounds: Bounds; + tileRows: (Tile | undefined | "")[][]; } export interface Executor { - account: unknown - enqueue(call: unknown, onSuccess: (result: unknown) => void, onFail: (error: unknown) => void): void - get pendingCalls(): number - set wallet(wallet: Wallet) + account: unknown; + enqueue( + call: unknown, + onSuccess: (result: unknown) => void, + onFail: (error: unknown) => void, + ): void; + get pendingCalls(): number; + set wallet(wallet: Wallet); } // Used for SmartContracts export type Position = { - x: number - y: number -} + x: number; + y: number; +}; -export type RGB = [number, number, number] +export type RGB = [number, number, number]; -export type Coordinate = [number, number] +export type Coordinate = [number, number]; -export type Bounds = [topLeft: Coordinate, bottomRight: Coordinate] +export type Bounds = [topLeft: Coordinate, bottomRight: Coordinate]; -export const MAX_DIMENSION: number = 32_767 // 2**15 -1 +export const MAX_DIMENSION: number = 32_767; // 2**15 -1 // Don't query everytime bounds change, but only when the buffer resolution changes // So when bounds change from 5 to 6, but Buffer is 10, no requery happens -export const QUERY_BUFFER: number = 20 +export const QUERY_BUFFER: number = 20; -export const TILESIZE = 100 +export const TILESIZE = 100; // TODO handle scalefactor 10 later -export const DEFAULT_SCALEFACTOR = 1 +export const DEFAULT_SCALEFACTOR = 1; export function makeString(coordinate: Coordinate): string { - if (!Array.isArray(coordinate) || coordinate.length !== 2) { - throw new Error("Invalid coordinate") - } - return `${coordinate[0]}_${coordinate[1]}` + if (!Array.isArray(coordinate) || coordinate.length !== 2) { + throw new Error("Invalid coordinate"); + } + return `${coordinate[0]}_${coordinate[1]}`; } -export type SimplePixelError = { coordinate: Coordinate | null; error: string } +export type SimplePixelError = { coordinate: Coordinate | null; error: string }; export type PixelCoreEvents = { - cellClicked: Coordinate - cellHovered: Coordinate | undefined - accountChanged: unknown | null // TODO should have a type for Account? - centerChanged: Coordinate - boundsChanged: Bounds - engineChanged: Engine - statusChanged: CoreStatus - walletChanged: Wallet - pixelStoreUpdated: number - tileStoreUpdated: number - appStoreUpdated: number - error: SimplePixelError - notification: Notification - userScrolled: { bounds: Bounds } - userZoomed: { bounds: Bounds } - cacheUpdated: number - appChanged: string | null - worldChanged: string | null - colorChanged: number - zoomChanged: number -} - -export type EngineStatus = "ready" | "loading" | "error" | "uninitialized" + cellClicked: Coordinate; + cellHovered: Coordinate | undefined; + accountChanged: unknown | null; // TODO should have a type for Account? + centerChanged: Coordinate; + boundsChanged: Bounds; + engineChanged: Engine; + statusChanged: CoreStatus; + walletChanged: Wallet; + pixelStoreUpdated: number; + tileStoreUpdated: number; + appStoreUpdated: number; + error: SimplePixelError; + notification: Notification; + userScrolled: { bounds: Bounds }; + userZoomed: { bounds: Bounds }; + cacheUpdated: number; + appChanged: string | null; + worldChanged: string | null; + colorChanged: number; + zoomChanged: number; +}; + +export type EngineStatus = "ready" | "loading" | "error" | "uninitialized"; export type CoreStatus = - | "uninitialized" - | "loadConfig" - | "initEngine" - | "initWallet" - | "initAccount" - | "ready" - | "readyWithoutWallet" - | "error" + | "uninitialized" + | "loadConfig" + | "initEngine" + | "initWallet" + | "initAccount" + | "ready" + | "readyWithoutWallet" + | "error"; export interface Engine { - id: Engines - core: PixelawCore - status: EngineStatus - - init(engineConfig: unknown): Promise - // setAccount(account: unknown): void - // handleInteraction(app: App, pixel: Pixel, color: number): Promise - prepInteraction(coordinate: Coordinate): Promise - // executeInteraction(interaction: Interaction): Promise - executeQueueItem(queueItem: QueueItem): Promise + id: Engines; + core: PixelawCore; + status: EngineStatus; + + init(engineConfig: unknown): Promise; + // setAccount(account: unknown): void + // handleInteraction(app: App, pixel: Pixel, color: number): Promise + prepInteraction(coordinate: Coordinate): Promise; + // executeInteraction(interaction: Interaction): Promise + executeQueueItem(queueItem: QueueItem): Promise; } -export type EngineConstructor = new (core: PixelawCore) => T +export type EngineConstructor = new (core: PixelawCore) => T; export interface WalletConfig { - masterAddress?: string - masterPrivateKey?: string - accountClassHash?: string - rpcUrl?: string - profileUrl?: string - url?: string + masterAddress?: string; + masterPrivateKey?: string; + accountClassHash?: string; + rpcUrl?: string; + profileUrl?: string; + url?: string; } -export type WorldsRegistry = Record +export type WorldsRegistry = Record; -export type Engines = "dojo" | "mud" +export type Engines = "dojo" | "mud"; export type WorldConfig = { - engine: Engines - description: string - defaults?: CoreDefaults - config: unknown -} + engine: Engines; + description: string; + defaults?: CoreDefaults; + config: unknown; +}; export type CoreDefaults = { - app: string - color: number - center: number[] // same as Coordinate - zoom: number -} + app: string; + color: number; + center: number[]; // same as Coordinate + zoom: number; +}; export type PixelStoreEvents = { - cacheUpdated: number -} + cacheUpdated: number; +}; export interface PixelStore { - eventEmitter: ReturnType> - refresh: () => void - prepare: (bounds: Bounds) => void - getPixel: (coordinate: Coordinate) => Pixel | undefined - setPixelColor: (coord: Coordinate, color: number) => void - setPixel: (key: string, pixel: Pixel) => void - setPixels: (pixels: Pixel[]) => void - unload?: () => Promise + eventEmitter: ReturnType>; + refresh: () => void; + prepare: (bounds: Bounds) => void; + getPixel: (coordinate: Coordinate) => Pixel | undefined; + setPixelColor: (coord: Coordinate, color: number) => void; + setPixel: (key: string, pixel: Pixel) => void; + setPixels: (pixels: Pixel[]) => void; + unload?: () => Promise; } export class BaseWallet { - engine: string - id: unknown - address: string - chainId: string - - constructor(engine: string, id: string, address: string, chainId: string) { - this.engine = engine - this.id = id - this.address = address - this.chainId = chainId - } + engine: string; + id: unknown; + address: string; + chainId: string; + + constructor(engine: string, id: string, address: string, chainId: string) { + this.engine = engine; + this.id = id; + this.address = address; + this.chainId = chainId; + } } export abstract class Wallet extends BaseWallet { - abstract toJSON(): unknown - abstract get isConnected(): boolean - abstract get account(): unknown + abstract toJSON(): unknown; + abstract get isConnected(): boolean; + abstract get account(): unknown; } -export const IS_BROWSER = typeof window !== "undefined" && typeof window.document !== "undefined" +export const IS_BROWSER = + typeof window !== "undefined" && typeof window.document !== "undefined"; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index b219254..355f955 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,57 +1,62 @@ -import type { Bounds, Coordinate } from "./types.ts" +import type { Bounds, Coordinate } from "./types.ts"; export function areBoundsEqual(boundsA: Bounds, boundsB: Bounds): boolean { - // Compare top-left coordinates - if (boundsA[0][0] !== boundsB[0][0] || boundsA[0][1] !== boundsB[0][1]) { - return false - } - // Compare bottom-right coordinates - if (boundsA[1][0] !== boundsB[1][0] || boundsA[1][1] !== boundsB[1][1]) { - return false - } - return true + // Compare top-left coordinates + if (boundsA[0][0] !== boundsB[0][0] || boundsA[0][1] !== boundsB[0][1]) { + return false; + } + // Compare bottom-right coordinates + if (boundsA[1][0] !== boundsB[1][0] || boundsA[1][1] !== boundsB[1][1]) { + return false; + } + return true; } -export function areCoordinatesEqual(coord1: Coordinate, coord2: Coordinate): boolean { - return coord1[0] === coord2[0] && coord1[1] === coord2[1] +export function areCoordinatesEqual( + coord1: Coordinate, + coord2: Coordinate, +): boolean { + return coord1[0] === coord2[0] && coord1[1] === coord2[1]; } export function sleepMs(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)); } export const numRGBAToHex = (rgba: number | undefined) => { - if (rgba === undefined) return "#0000EE" // TODO Maybe return default color? - const color = rgba >>> 8 - return `#${color.toString(16).padStart(6, "0")}` -} + if (rgba === undefined) return "#0000EE"; // TODO Maybe return default color? + const color = rgba >>> 8; + return `#${color.toString(16).padStart(6, "0")}`; +}; export function hexToRgb(input: string): [number, number, number] { - // Remove the hash at the start if it's there - const hex = input.replace(/^#/, "") + // Remove the hash at the start if it's there + const hex = input.replace(/^#/, ""); - // Parse the r, g, b values - const bigint = Number.parseInt(hex, 16) - const r = (bigint >> 16) & 255 - const g = (bigint >> 8) & 255 - const b = bigint & 255 + // Parse the r, g, b values + const bigint = Number.parseInt(hex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; - return [r, g, b] + return [r, g, b]; } -export function parsePixelError(input: string): { coordinate: Coordinate; error: string } | null { - const regex = /^(\d+)_(\d+)\s+(.+)$/ - const match = input.match(regex) +export function parsePixelError( + input: string, +): { coordinate: Coordinate; error: string } | null { + const regex = /^(\d+)_(\d+)\s+(.+)$/; + const match = input.match(regex); - if (match) { - const x = Number.parseInt(match[1], 10) - const y = Number.parseInt(match[2], 10) - const error = match[3] + if (match) { + const x = Number.parseInt(match[1], 10); + const y = Number.parseInt(match[2], 10); + const error = match[3]; - return { - coordinate: [x, y], - error, - } - } return { - coordinate: null, - error: input, - } + coordinate: [x, y], + error, + }; + } + return { + coordinate: null, + error: input, + }; } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index d80d439..2511b70 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -19,8 +19,7 @@ "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - "paths": { - } + "paths": {} }, "include": ["src"] } diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts index d8f3679..c04471d 100644 --- a/packages/core/tsup.config.ts +++ b/packages/core/tsup.config.ts @@ -1,7 +1,7 @@ -import { type Options, defineConfig } from "tsup" +import { type Options, defineConfig } from "tsup"; -import { tsupConfig } from "../../tsup.config" +import { tsupConfig } from "../../tsup.config"; export default defineConfig({ - ...(tsupConfig as Options), -}) + ...(tsupConfig as Options), +}); diff --git a/packages/imgtool-dojo/src/__tests__/main.test.ts b/packages/imgtool-dojo/src/__tests__/main.test.ts index 3e39131..486259a 100644 --- a/packages/imgtool-dojo/src/__tests__/main.test.ts +++ b/packages/imgtool-dojo/src/__tests__/main.test.ts @@ -1,54 +1,54 @@ -import {PNG} from "pngjs" -import {beforeAll, describe, expect, it} from "vitest" -import {DojoImageTool} from "../dojo-image-tool.ts"; +import { PNG } from "pngjs"; +import { beforeAll, describe, expect, it } from "vitest"; +import { DojoImageTool } from "../dojo-image-tool.ts"; describe("PngToFeltEncoder", () => { - let imageBuffer: Buffer - - beforeAll(() => { - // Prepare a small PNG buffer for testing - const png = new PNG({ width: 2, height: 2 }) - png.data = Buffer.from([ - 0, - 0, - 0, - 255, // Black pixel - 255, - 255, - 255, - 255, // White pixel - 255, - 0, - 0, - 255, // Red pixel - 0, - 255, - 0, - 255, // Green pixel - ]) - imageBuffer = PNG.sync.write(png) - }) - - it("should generate correct pixel rows", () => { - const encoder = new DojoImageTool(imageBuffer, { x: 0, y: 0 }) - const pixelRows = encoder.generatePixelRows() - - expect(pixelRows).toBeInstanceOf(Array) - expect(pixelRows.length).toBeGreaterThan(0) - - // Further assertions can be made based on expected output - expect(pixelRows[0]).toHaveProperty("position") - expect(pixelRows[0]).toHaveProperty("image_data") - }) - - it("should generate correct Sozo command string", () => { - const encoder = new DojoImageTool(imageBuffer, { x: 0, y: 0 }) - const pixelRows = encoder.generatePixelRows() - const sozoCommand = encoder.generateSozo(pixelRows) - - expect(typeof sozoCommand).toBe("string") - expect(sozoCommand).toContain("sozo") - }) - - // Add more tests as needed for other methods -}) + let imageBuffer: Buffer; + + beforeAll(() => { + // Prepare a small PNG buffer for testing + const png = new PNG({ width: 2, height: 2 }); + png.data = Buffer.from([ + 0, + 0, + 0, + 255, // Black pixel + 255, + 255, + 255, + 255, // White pixel + 255, + 0, + 0, + 255, // Red pixel + 0, + 255, + 0, + 255, // Green pixel + ]); + imageBuffer = PNG.sync.write(png); + }); + + it("should generate correct pixel rows", () => { + const encoder = new DojoImageTool(imageBuffer, { x: 0, y: 0 }); + const pixelRows = encoder.generatePixelRows(); + + expect(pixelRows).toBeInstanceOf(Array); + expect(pixelRows.length).toBeGreaterThan(0); + + // Further assertions can be made based on expected output + expect(pixelRows[0]).toHaveProperty("position"); + expect(pixelRows[0]).toHaveProperty("image_data"); + }); + + it("should generate correct Sozo command string", () => { + const encoder = new DojoImageTool(imageBuffer, { x: 0, y: 0 }); + const pixelRows = encoder.generatePixelRows(); + const sozoCommand = encoder.generateSozo(pixelRows); + + expect(typeof sozoCommand).toBe("string"); + expect(sozoCommand).toContain("sozo"); + }); + + // Add more tests as needed for other methods +}); diff --git a/packages/imgtool-dojo/src/dojo-image-tool.ts b/packages/imgtool-dojo/src/dojo-image-tool.ts index 96591a5..5ba4bd5 100644 --- a/packages/imgtool-dojo/src/dojo-image-tool.ts +++ b/packages/imgtool-dojo/src/dojo-image-tool.ts @@ -1,165 +1,170 @@ -import { Writable } from "node:stream" -import { PNG } from "pngjs" -import { Account, CallData, Contract, RpcProvider } from "starknet" -import * as fs from "node:fs" +import { Writable } from "node:stream"; +import { PNG } from "pngjs"; +import { Account, CallData, Contract, RpcProvider } from "starknet"; +import * as fs from "node:fs"; -const entrypoint = "pixel_row" -type PixelRow = { position: { x: number; y: number }; image_data: string[] } -type PixelRows = PixelRow[] +const entrypoint = "pixel_row"; +type PixelRow = { position: { x: number; y: number }; image_data: string[] }; +type PixelRows = PixelRow[]; export class DojoImageTool { - private imageBuffer: Buffer + private imageBuffer: Buffer; - constructor(imageBuffer: Buffer) { - this.imageBuffer = imageBuffer - } + constructor(imageBuffer: Buffer) { + this.imageBuffer = imageBuffer; + } - static fromFile(filePath: string): DojoImageTool { - const imageBuffer = fs.readFileSync(filePath) - return new DojoImageTool(imageBuffer) - } + static fromFile(filePath: string): DojoImageTool { + const imageBuffer = fs.readFileSync(filePath); + return new DojoImageTool(imageBuffer); + } - static createPng(size: number): Buffer { - const png = new PNG({ width: size, height: size }) + static createPng(size: number): Buffer { + const png = new PNG({ width: size, height: size }); - for (let y = 0; y < png.height; y++) { - for (let x = 0; x < png.width; x++) { - const idx = (png.width * y + x) << 2 + for (let y = 0; y < png.height; y++) { + for (let x = 0; x < png.width; x++) { + const idx = (png.width * y + x) << 2; - // Create gradient from red to blue - const red = Math.round(255 * (1 - y / png.height)) - const blue = Math.round((255 * y) / png.height) + // Create gradient from red to blue + const red = Math.round(255 * (1 - y / png.height)); + const blue = Math.round((255 * y) / png.height); - png.data[idx] = red - png.data[idx + 1] = 0 - png.data[idx + 2] = blue - png.data[idx + 3] = 255 - } - } + png.data[idx] = red; + png.data[idx + 1] = 0; + png.data[idx + 2] = blue; + png.data[idx + 3] = 255; + } + } - const chunks: Buffer[] = [] - const bufferStream = new Writable({ - write(chunk, _encoding, callback) { - chunks.push(chunk) - callback() - }, - }) + const chunks: Buffer[] = []; + const bufferStream = new Writable({ + write(chunk, _encoding, callback) { + chunks.push(chunk); + callback(); + }, + }); - png.pack().pipe(bufferStream) + png.pack().pipe(bufferStream); - return Buffer.concat(chunks) - } + return Buffer.concat(chunks); + } + + generatePixelRows(): PixelRows { + const pixelRows = []; + const png = PNG.sync.read(this.imageBuffer); - generatePixelRows(): PixelRows { - const pixelRows = [] - const png = PNG.sync.read(this.imageBuffer) + const pixels = []; + for (let i = 0; i < png.data.length; i += 4) { + pixels.push(Array.from(png.data.slice(i, i + 4))); + } - const pixels = [] - for (let i = 0; i < png.data.length; i += 4) { - pixels.push(Array.from(png.data.slice(i, i + 4))) + const BUFFER_SIZE = 1000; + let buffer = []; + const PIXELS_PER_FELT = 7; + let x_offset = 0; + for (let last_pixel = 0; last_pixel < pixels.length; last_pixel++) { + const x = last_pixel % png.width; + const y = Math.floor(last_pixel / png.width); + + buffer.push(pixels[last_pixel]); + + if ( + buffer.length === BUFFER_SIZE || + x === png.width - 1 || + last_pixel === pixels.length - 1 + ) { + const feltPixels: number[][] = []; + for (let i = 0; i < buffer.length; i += PIXELS_PER_FELT) { + const chunk = buffer.slice(i, i + PIXELS_PER_FELT).flat(); + feltPixels.push(chunk); } - const BUFFER_SIZE = 1000 - let buffer = [] - const PIXELS_PER_FELT = 7 - let x_offset = 0 - for (let last_pixel = 0; last_pixel < pixels.length; last_pixel++) { - const x = last_pixel % png.width - const y = Math.floor(last_pixel / png.width) - - buffer.push(pixels[last_pixel]) - - if (buffer.length === BUFFER_SIZE || x === png.width - 1 || last_pixel === pixels.length - 1) { - const feltPixels: number[][] = [] - for (let i = 0; i < buffer.length; i += PIXELS_PER_FELT) { - const chunk = buffer.slice(i, i + PIXELS_PER_FELT).flat() - feltPixels.push(chunk) - } - - const image_data: string[] = feltPixels.map((pixel) => { - const buf = Buffer.from(pixel) - const hexString = buf.toString("hex") - return "0x0000000".concat(hexString).padEnd(65, "0") - }) - - buffer = [] - - pixelRows.push({ position: { x: x_offset, y: y }, image_data }) - - if (x === png.width - 1) { - x_offset = 0 - } else { - x_offset = x - } - } + const image_data: string[] = feltPixels.map((pixel) => { + const buf = Buffer.from(pixel); + const hexString = buf.toString("hex"); + return "0x0000000".concat(hexString).padEnd(65, "0"); + }); + + buffer = []; + + pixelRows.push({ position: { x: x_offset, y: y }, image_data }); + + if (x === png.width - 1) { + x_offset = 0; + } else { + x_offset = x; } - return pixelRows + } } + return pixelRows; + } - generateSozo(pRows?: PixelRows): string { - const pixelRows = pRows ?? this.generatePixelRows() + generateSozo(pRows?: PixelRows): string { + const pixelRows = pRows ?? this.generatePixelRows(); - let result = "" - for (const { position, image_data } of pixelRows) { - result += `sozo execute --wait pixelaw-paint_actions pixel_row \ + let result = ""; + for (const { position, image_data } of pixelRows) { + result += `sozo execute --wait pixelaw-paint_actions pixel_row \ 1 1 1 ${position.x} ${position.y} 0 ${image_data.length} ${image_data.join(" ")} -` - } - return result +`; } + return result; + } - async execute(pRows: PixelRows) { - const pixelRows = pRows ?? this.generatePixelRows() + async execute(pRows: PixelRows) { + const pixelRows = pRows ?? this.generatePixelRows(); - const provider = new RpcProvider({ nodeUrl: "http://127.0.0.1:5050" }) + const provider = new RpcProvider({ nodeUrl: "http://127.0.0.1:5050" }); - const account0 = new Account( - provider, - "0x003c4dd268780ef738920c801edc3a75b6337bc17558c74795b530c0ff502486", - "0x2bbf4f9fd0bbb2e60b0316c1fe0b76cf7a4d0198bd493ced9b8df2a3a24d68a", - ) + const account0 = new Account( + provider, + "0x003c4dd268780ef738920c801edc3a75b6337bc17558c74795b530c0ff502486", + "0x2bbf4f9fd0bbb2e60b0316c1fe0b76cf7a4d0198bd493ced9b8df2a3a24d68a", + ); - const contractAddress = "0x1f04b61e71f2afa9610c422db007807f73ebad6b4c069e72bb6e22ff032a93c" - const { abi } = await provider.getClassAt(contractAddress) - if (abi === undefined) { - throw new Error("no abi.") - } - const myTestContract = new Contract(abi, contractAddress, provider) - myTestContract.connect(account0) - - for (const { position, image_data } of pixelRows) { - const defaultParams = { - for_player: 0, - for_system: 0, - position, - color: "0xAFAFAF", - } - - const calldata = CallData.compile({ - defaultParams, - image_data, - }) - - try { - const result = await account0.execute({ - contractAddress, - entrypoint, - calldata, - }) - await provider.waitForTransaction(result.transaction_hash) - - console.log({ result }) - } catch (e) { - console.error({ calldata }, e) - } - } + const contractAddress = + "0x1f04b61e71f2afa9610c422db007807f73ebad6b4c069e72bb6e22ff032a93c"; + const { abi } = await provider.getClassAt(contractAddress); + if (abi === undefined) { + throw new Error("no abi."); } - - async run() { - const pixelRows = await this.generatePixelRows() - console.log(this.generateSozo(pixelRows)) - await this.execute(pixelRows) + const myTestContract = new Contract(abi, contractAddress, provider); + myTestContract.connect(account0); + + for (const { position, image_data } of pixelRows) { + const defaultParams = { + for_player: 0, + for_system: 0, + position, + color: "0xAFAFAF", + }; + + const calldata = CallData.compile({ + defaultParams, + image_data, + }); + + try { + const result = await account0.execute({ + contractAddress, + entrypoint, + calldata, + }); + await provider.waitForTransaction(result.transaction_hash); + + console.log({ result }); + } catch (e) { + console.error({ calldata }, e); + } } + } + + async run() { + const pixelRows = await this.generatePixelRows(); + console.log(this.generateSozo(pixelRows)); + await this.execute(pixelRows); + } } // // Example usage with a buffer // ;(async () => { diff --git a/packages/imgtool-dojo/src/index.ts b/packages/imgtool-dojo/src/index.ts index 260dad8..0ed1e94 100644 --- a/packages/imgtool-dojo/src/index.ts +++ b/packages/imgtool-dojo/src/index.ts @@ -1,2 +1,2 @@ -export * from "./types.ts" -export * from "./dojo-image-tool.ts" +export * from "./types.ts"; +export * from "./dojo-image-tool.ts"; diff --git a/packages/imgtool-dojo/src/process-initial.ts b/packages/imgtool-dojo/src/process-initial.ts index b33ecd0..62b948b 100644 --- a/packages/imgtool-dojo/src/process-initial.ts +++ b/packages/imgtool-dojo/src/process-initial.ts @@ -1,14 +1,14 @@ -import {DojoImageTool} from "./dojo-image-tool"; // Adjust the import path as necessary +import { DojoImageTool } from "./dojo-image-tool"; // Adjust the import path as necessary async function main(filePath: string) { - try { - const dojoImageTool = DojoImageTool.fromFile(filePath) - const sozoScript = dojoImageTool.generateSozo() - console.log(sozoScript) - } catch (error) { - console.error("Error generating Sozo script:", error) - } + try { + const dojoImageTool = DojoImageTool.fromFile(filePath); + const sozoScript = dojoImageTool.generateSozo(); + console.log(sozoScript); + } catch (error) { + console.error("Error generating Sozo script:", error); + } } // Example usage -main("./data/initial.png") +main("./data/initial.png"); diff --git a/packages/imgtool-dojo/tsup.config.ts b/packages/imgtool-dojo/tsup.config.ts index d8f3679..c04471d 100644 --- a/packages/imgtool-dojo/tsup.config.ts +++ b/packages/imgtool-dojo/tsup.config.ts @@ -1,7 +1,7 @@ -import { type Options, defineConfig } from "tsup" +import { type Options, defineConfig } from "tsup"; -import { tsupConfig } from "../../tsup.config" +import { tsupConfig } from "../../tsup.config"; export default defineConfig({ - ...(tsupConfig as Options), -}) + ...(tsupConfig as Options), +}); diff --git a/packages/imgtool-dojo/vitest.config.ts b/packages/imgtool-dojo/vitest.config.ts index 2d182ab..8fb6f2d 100644 --- a/packages/imgtool-dojo/vitest.config.ts +++ b/packages/imgtool-dojo/vitest.config.ts @@ -1,3 +1,3 @@ -import {defineConfig} from "vitest/config"; +import { defineConfig } from "vitest/config"; -export default defineConfig({}) +export default defineConfig({}); diff --git a/packages/react-dojo/postcss.config.js b/packages/react-dojo/postcss.config.js index d8cd824..3e0d24c 100644 --- a/packages/react-dojo/postcss.config.js +++ b/packages/react-dojo/postcss.config.js @@ -1,6 +1,3 @@ export default { - plugins: { - - }, - } - \ No newline at end of file + plugins: {}, +}; diff --git a/packages/react-dojo/src/components/ControllerDetails/ControllerDetails.tsx b/packages/react-dojo/src/components/ControllerDetails/ControllerDetails.tsx index 99ee00d..5177c25 100644 --- a/packages/react-dojo/src/components/ControllerDetails/ControllerDetails.tsx +++ b/packages/react-dojo/src/components/ControllerDetails/ControllerDetails.tsx @@ -1,61 +1,63 @@ -import type ControllerConnector from "@cartridge/connector/controller" -import type React from "react" -import { useEffect, useState } from "react" -import styles from "./ControllerDetails.module.css" +import type ControllerConnector from "@cartridge/connector/controller"; +import type React from "react"; +import { useEffect, useState } from "react"; +import styles from "./ControllerDetails.module.css"; interface ControllerDetailsProps { - connector: ControllerConnector + connector: ControllerConnector; } -export const ControllerDetails: React.FC = ({ connector }) => { - const [username, setUsername] = useState("") - - useEffect(() => { - if (!connector) return - - connector.username().then((username) => { - setUsername(username) - }) - - console.log(connector.controller.account) - }, [connector]) - - const openProfile = async () => { - if (!connector) return - await connector.controller.openProfile() - } - const openSettings = async () => { - if (!connector) return - await connector.controller.openSettings() - } - const disconnect = async () => { - if (!connector) return - await connector.controller.disconnect() - } - const connect = async () => { - if (!connector) return - await connector.controller.connect() - } - - return ( -
-

Username: {connector ? username : "No controller"}

-

- - - - -

-
- ) -} - -export default ControllerDetails +export const ControllerDetails: React.FC = ({ + connector, +}) => { + const [username, setUsername] = useState(""); + + useEffect(() => { + if (!connector) return; + + connector.username().then((username) => { + setUsername(username); + }); + + console.log(connector.controller.account); + }, [connector]); + + const openProfile = async () => { + if (!connector) return; + await connector.controller.openProfile(); + }; + const openSettings = async () => { + if (!connector) return; + await connector.controller.openSettings(); + }; + const disconnect = async () => { + if (!connector) return; + await connector.controller.disconnect(); + }; + const connect = async () => { + if (!connector) return; + await connector.controller.connect(); + }; + + return ( +
+

Username: {connector ? username : "No controller"}

+

+ + + + +

+
+ ); +}; + +export default ControllerDetails; diff --git a/packages/react-dojo/src/hooks/ConnectorProvider.tsx b/packages/react-dojo/src/hooks/ConnectorProvider.tsx index 8a656f4..0c95d0b 100644 --- a/packages/react-dojo/src/hooks/ConnectorProvider.tsx +++ b/packages/react-dojo/src/hooks/ConnectorProvider.tsx @@ -1,143 +1,180 @@ -import { DojoWallet, type DojoWalletId } from "@pixelaw/core-dojo" -import type { DojoEngine } from "@pixelaw/core-dojo/src" -import { usePixelawProvider } from "@pixelaw/react" -import type { Connector } from "@starknet-react/core" -import { InjectedConnector, useAccount, useConnect, useDisconnect } from "@starknet-react/core" - -import { createContext, type ReactNode, useContext, useEffect, useState } from "react" -import { constants } from "starknet" -import { ArgentMobileConnector, isInArgentMobileAppBrowser } from "starknetkit/argentMobile" -import { WebWalletConnector } from "starknetkit/webwallet" +import { DojoWallet, type DojoWalletId } from "@pixelaw/core-dojo"; +import type { DojoEngine } from "@pixelaw/core-dojo/src"; +import { usePixelawProvider } from "@pixelaw/react"; +import type { Connector } from "@starknet-react/core"; +import { + InjectedConnector, + useAccount, + useConnect, + useDisconnect, +} from "@starknet-react/core"; + +import { + createContext, + type ReactNode, + useContext, + useEffect, + useState, +} from "react"; +import { constants } from "starknet"; +import { + ArgentMobileConnector, + isInArgentMobileAppBrowser, +} from "starknetkit/argentMobile"; +import { WebWalletConnector } from "starknetkit/webwallet"; interface ConnectorContextType { - availableConnectors: Connector[] - handleConnectorSelection: (connector: Connector | null) => Promise + availableConnectors: Connector[]; + handleConnectorSelection: (connector: Connector | null) => Promise; } interface ConnectorProviderProps { - children: ReactNode + children: ReactNode; } -const ConnectorContext = createContext(undefined) - -export const ConnectorProvider: React.FC = ({ children }) => { - const [availableConnectors, setAvailableConnectors] = useState([]) - const { connectAsync } = useConnect() - const { disconnectAsync } = useDisconnect() - const { connector: currentConnector, account: currentAccount } = useAccount() - const { pixelawCore, wallet, setWallet, coreStatus } = usePixelawProvider() - - const engine: DojoEngine = pixelawCore.engine as DojoEngine - const { controllerConnector, burnerConnector } = engine.dojoSetup || {} - - // setting AvailableConnectors - useEffect(() => { - const connectors: Connector[] = [ - burnerConnector as unknown as Connector, - // ...(isInArgentMobileAppBrowser() - // ? [ - // ArgentMobileConnector.init({ - // options: { - // url: window?.location.href || "", - // dappName: "PixeLAW", - // chainId: constants.NetworkName.SN_SEPOLIA, - // }, - // }), - // ] - // : [ - // new InjectedConnector({ options: { id: "argentX" } }), - // new InjectedConnector({ options: { id: "braavos" } }), - // ArgentMobileConnector.init({ - // options: { - // url: window?.location.href || "", - // dappName: "PixeLAW", - // chainId: constants.NetworkName.SN_MAIN, - // }, - // }), - // new WebWalletConnector({ url: "https://web.argent.xyz" }), - // ]), - controllerConnector, - ].filter((connector): connector is Connector => connector != null) - - setAvailableConnectors(connectors) - }, [controllerConnector, burnerConnector]) - - // Handling connector selection (click in the wallet selector page) - const handleConnectorSelection = async (connector: Connector | null) => { - try { - // Disconnect the current connector first - if (currentConnector) await disconnectAsync() - - // If a connector was selected, connect to it. - if (connector) { - await connectAsync({ connector }) - } else { - setWallet(null) - } - } catch (error) { - console.error("Error activating connector:", error) - } +const ConnectorContext = createContext( + undefined, +); + +export const ConnectorProvider: React.FC = ({ + children, +}) => { + const [availableConnectors, setAvailableConnectors] = useState( + [], + ); + const { connectAsync } = useConnect(); + const { disconnectAsync } = useDisconnect(); + const { connector: currentConnector, account: currentAccount } = useAccount(); + const { pixelawCore, wallet, setWallet, coreStatus } = usePixelawProvider(); + + const engine: DojoEngine = pixelawCore.engine as DojoEngine; + const { controllerConnector, burnerConnector } = engine.dojoSetup || {}; + + // setting AvailableConnectors + useEffect(() => { + const connectors: Connector[] = [ + burnerConnector as unknown as Connector, + // ...(isInArgentMobileAppBrowser() + // ? [ + // ArgentMobileConnector.init({ + // options: { + // url: window?.location.href || "", + // dappName: "PixeLAW", + // chainId: constants.NetworkName.SN_SEPOLIA, + // }, + // }), + // ] + // : [ + // new InjectedConnector({ options: { id: "argentX" } }), + // new InjectedConnector({ options: { id: "braavos" } }), + // ArgentMobileConnector.init({ + // options: { + // url: window?.location.href || "", + // dappName: "PixeLAW", + // chainId: constants.NetworkName.SN_MAIN, + // }, + // }), + // new WebWalletConnector({ url: "https://web.argent.xyz" }), + // ]), + controllerConnector, + ].filter((connector): connector is Connector => connector != null); + + setAvailableConnectors(connectors); + }, [controllerConnector, burnerConnector]); + + // Handling connector selection (click in the wallet selector page) + const handleConnectorSelection = async (connector: Connector | null) => { + try { + // Disconnect the current connector first + if (currentConnector) await disconnectAsync(); + + // If a connector was selected, connect to it. + if (connector) { + await connectAsync({ connector }); + } else { + setWallet(null); + } + } catch (error) { + console.error("Error activating connector:", error); } + }; - // Handling Connector activation/deactivation - useEffect(() => { - const setCoreWalletFromConnector = async () => { - if (currentConnector && currentAccount) { - try { - console.log("activateConnector", { currentAccount }) - if (!pixelawCore.wallet) { - const chainId = await currentAccount.getChainId() - - const wallet = new DojoWallet(currentConnector.id as DojoWalletId, chainId, currentAccount) - - pixelawCore.wallet = wallet - } - pixelawCore.account = currentAccount - } catch (error) { - console.error("Error setting up wallet:", error) - } - } + // Handling Connector activation/deactivation + useEffect(() => { + const setCoreWalletFromConnector = async () => { + if (currentConnector && currentAccount) { + try { + console.log("activateConnector", { currentAccount }); + if (!pixelawCore.wallet) { + const chainId = await currentAccount.getChainId(); + + const wallet = new DojoWallet( + currentConnector.id as DojoWalletId, + chainId, + currentAccount, + ); + + pixelawCore.wallet = wallet; + } + pixelawCore.account = currentAccount; + } catch (error) { + console.error("Error setting up wallet:", error); } - - setCoreWalletFromConnector() - }, [currentConnector, currentAccount, pixelawCore]) - - // Handling loading from BaseWallet - useEffect(() => { - const loadAccountForWallet = async () => { - try { - if (availableConnectors && coreStatus === "initAccount" && wallet) { - console.log("loadAccountForWallet", availableConnectors) - // Find the connector belonging to core.wallet - const matchingConnector = availableConnectors.find((connector) => connector.id === wallet.id) - - // If found, connect it in the browser - if (matchingConnector) { - console.log("connecting", matchingConnector) - await connectAsync({ connector: matchingConnector }) - pixelawCore.account = currentAccount - } - console.log("done") - } - } catch (e) { - console.error(e) - } + } + }; + + setCoreWalletFromConnector(); + }, [currentConnector, currentAccount, pixelawCore]); + + // Handling loading from BaseWallet + useEffect(() => { + const loadAccountForWallet = async () => { + try { + if (availableConnectors && coreStatus === "initAccount" && wallet) { + console.log("loadAccountForWallet", availableConnectors); + // Find the connector belonging to core.wallet + const matchingConnector = availableConnectors.find( + (connector) => connector.id === wallet.id, + ); + + // If found, connect it in the browser + if (matchingConnector) { + console.log("connecting", matchingConnector); + await connectAsync({ connector: matchingConnector }); + pixelawCore.account = currentAccount; + } + console.log("done"); } - - loadAccountForWallet() - }, [wallet, availableConnectors, connectAsync, coreStatus, currentAccount, pixelawCore]) - - return ( - - {children} - - ) -} + } catch (e) { + console.error(e); + } + }; + + loadAccountForWallet(); + }, [ + wallet, + availableConnectors, + connectAsync, + coreStatus, + currentAccount, + pixelawCore, + ]); + + return ( + + {children} + + ); +}; export const useConnectorContext = () => { - const context = useContext(ConnectorContext) - if (!context) { - throw new Error("useConnectorContext must be used within a ConnectorProvider") - } - return context -} + const context = useContext(ConnectorContext); + if (!context) { + throw new Error( + "useConnectorContext must be used within a ConnectorProvider", + ); + } + return context; +}; diff --git a/packages/react-dojo/src/hooks/StarknetChainProvider.tsx b/packages/react-dojo/src/hooks/StarknetChainProvider.tsx index 012cd04..98e57bc 100644 --- a/packages/react-dojo/src/hooks/StarknetChainProvider.tsx +++ b/packages/react-dojo/src/hooks/StarknetChainProvider.tsx @@ -1,12 +1,18 @@ -import type { ChainProviderProps } from "@pixelaw/react" -import { mainnet, sepolia } from "@starknet-react/chains" -import { StarknetConfig, publicProvider } from "@starknet-react/core" -import { ConnectorProvider } from "./ConnectorProvider" +import type { ChainProviderProps } from "@pixelaw/react"; +import { mainnet, sepolia } from "@starknet-react/chains"; +import { StarknetConfig, publicProvider } from "@starknet-react/core"; +import { ConnectorProvider } from "./ConnectorProvider"; -export const StarknetChainProvider: React.FC = ({ children }) => { - return ( - - {children} - - ) -} +export const StarknetChainProvider: React.FC = ({ + children, +}) => { + return ( + + {children} + + ); +}; diff --git a/packages/react-dojo/src/index.ts b/packages/react-dojo/src/index.ts index 6766ef3..2de1248 100644 --- a/packages/react-dojo/src/index.ts +++ b/packages/react-dojo/src/index.ts @@ -1,2 +1,2 @@ -export * from "./hooks/StarknetChainProvider" -export * from "./pages/StarknetWalletSelectorPage" +export * from "./hooks/StarknetChainProvider"; +export * from "./pages/StarknetWalletSelectorPage"; diff --git a/packages/react-dojo/src/pages/StarknetWalletSelectorPage.module.css b/packages/react-dojo/src/pages/StarknetWalletSelectorPage.module.css index be2fe66..5a2ba90 100644 --- a/packages/react-dojo/src/pages/StarknetWalletSelectorPage.module.css +++ b/packages/react-dojo/src/pages/StarknetWalletSelectorPage.module.css @@ -11,43 +11,43 @@ /*}*/ .list { - list-style-type: none; - padding: 0; + list-style-type: none; + padding: 0; } .listItem { - padding: 10px; - cursor: pointer; - border: 1px solid #ccc; - margin-bottom: 5px; - border-radius: 4px; - transition: background-color 0.3s; + padding: 10px; + cursor: pointer; + border: 1px solid #ccc; + margin-bottom: 5px; + border-radius: 4px; + transition: background-color 0.3s; } .listItem:hover { - background-color: #f0f0f0; + background-color: #f0f0f0; } .selected { - background-color: #d0eaff; - border-color: #a0cfff; + background-color: #d0eaff; + border-color: #a0cfff; } .menuButton { - width: 100%; - padding: 10px; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; + width: 100%; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; } .selectedButton { - background-color: #007bff; - color: white; + background-color: #007bff; + color: white; } .unselectedButton { - background-color: #6c757d; - color: white; + background-color: #6c757d; + color: white; } diff --git a/packages/react-dojo/src/pages/StarknetWalletSelectorPage.tsx b/packages/react-dojo/src/pages/StarknetWalletSelectorPage.tsx index 86be50f..45c9eb8 100644 --- a/packages/react-dojo/src/pages/StarknetWalletSelectorPage.tsx +++ b/packages/react-dojo/src/pages/StarknetWalletSelectorPage.tsx @@ -1,48 +1,59 @@ -import type ControllerConnector from "@cartridge/connector/controller" -import { useAccount } from "@starknet-react/core" +import type ControllerConnector from "@cartridge/connector/controller"; +import { useAccount } from "@starknet-react/core"; -import ControllerDetails from "../components/ControllerDetails/ControllerDetails" -import { useConnectorContext } from "../hooks/ConnectorProvider" -import styles from "./StarknetWalletSelectorPage.module.css" +import ControllerDetails from "../components/ControllerDetails/ControllerDetails"; +import { useConnectorContext } from "../hooks/ConnectorProvider"; +import styles from "./StarknetWalletSelectorPage.module.css"; export const StarknetWalletSelectorPage = () => { - const { connector: currentConnector, account: currentAccount, status } = useAccount() - const { availableConnectors, handleConnectorSelection } = useConnectorContext() + const { + connector: currentConnector, + account: currentAccount, + status, + } = useAccount(); + const { availableConnectors, handleConnectorSelection } = + useConnectorContext(); - return ( -
- {currentConnector?.id === "controller" && ( - - )} -

- {currentConnector ? currentConnector.id : "none"} {status}{" "} - {currentAccount ? currentAccount.address : "no account"} -

+ return ( +
+ {currentConnector?.id === "controller" && ( + + )} +

+ {currentConnector ? currentConnector.id : "none"} {status}{" "} + {currentAccount ? currentAccount.address : "no account"} +

-

Wallet Selector

-
    -
  • - -
  • - {availableConnectors.map((availConnector) => ( -
  • - -
  • - ))} -
-
- ) -} +

Wallet Selector

+
    +
  • + +
  • + {availableConnectors.map((availConnector) => ( +
  • + +
  • + ))} +
+
+ ); +}; -export default StarknetWalletSelectorPage +export default StarknetWalletSelectorPage; diff --git a/packages/react-dojo/tsup.config.ts b/packages/react-dojo/tsup.config.ts index d8f3679..c04471d 100644 --- a/packages/react-dojo/tsup.config.ts +++ b/packages/react-dojo/tsup.config.ts @@ -1,7 +1,7 @@ -import { type Options, defineConfig } from "tsup" +import { type Options, defineConfig } from "tsup"; -import { tsupConfig } from "../../tsup.config" +import { tsupConfig } from "../../tsup.config"; export default defineConfig({ - ...(tsupConfig as Options), -}) + ...(tsupConfig as Options), +}); diff --git a/packages/react/src/components/InteractionDialog.tsx b/packages/react/src/components/InteractionDialog.tsx index 631627f..19dc3fb 100644 --- a/packages/react/src/components/InteractionDialog.tsx +++ b/packages/react/src/components/InteractionDialog.tsx @@ -1,113 +1,124 @@ -import type React from "react" -import { useState } from "react" -import type { Interaction, InteractParam } from "@pixelaw/core" -import EmojiPicker, { type EmojiClickData } from "emoji-picker-react" +import type React from "react"; +import { useState } from "react"; +import type { Interaction, InteractParam } from "@pixelaw/core"; +import EmojiPicker, { type EmojiClickData } from "emoji-picker-react"; export interface InteractionDialogProps { - interaction: Interaction - onSubmit: (interaction: Interaction) => void - onCancel: (interaction: Interaction) => void + interaction: Interaction; + onSubmit: (interaction: Interaction) => void; + onCancel: (interaction: Interaction) => void; } -export const InteractionDialog: React.FC = ({ interaction, onSubmit, onCancel }) => { - const [emojiPickerForParam, setEmojiPickerForParam] = useState(null) - const params = interaction.getUserParams() +export const InteractionDialog: React.FC = ({ + interaction, + onSubmit, + onCancel, +}) => { + const [emojiPickerForParam, setEmojiPickerForParam] = useState( + null, + ); + const params = interaction.getUserParams(); - const handleCancel = () => onCancel(interaction) + const handleCancel = () => onCancel(interaction); - const handleSubmit = async () => { - await interaction.execute() - onSubmit(interaction) - } + const handleSubmit = async () => { + await interaction.execute(); + onSubmit(interaction); + }; - const renderInput = (param: InteractParam) => { - const commonProps = { - value: param.value || "", - } - if (param.type === "enum" && !param.value && param.variants.length > 0) { - // Set the initial value for the enum if not already set - interaction.setUserParam(param.name, param.variants[0].value) - } - switch (param.type) { - case "string": - return - case "number": - return ( - interaction.setUserParam(param.name, Number.parseInt(e.target.value))} - /> - ) - case "enum": - return ( - - ) - case "emoji": - return ( - ) => - interaction.setUserParam(param.name, e.target.value) - } - onClick={() => setEmojiPickerForParam(param.name)} - /> - ) - default: - return null - } + const renderInput = (param: InteractParam) => { + const commonProps = { + value: param.value || "", + }; + if (param.type === "enum" && !param.value && param.variants.length > 0) { + // Set the initial value for the enum if not already set + interaction.setUserParam(param.name, param.variants[0].value); + } + switch (param.type) { + case "string": + return ; + case "number": + return ( + + interaction.setUserParam( + param.name, + Number.parseInt(e.target.value), + ) + } + /> + ); + case "enum": + return ( + + ); + case "emoji": + return ( + , + ) => interaction.setUserParam(param.name, e.target.value)} + onClick={() => setEmojiPickerForParam(param.name)} + /> + ); + default: + return null; } + }; - return ( -
- {emojiPickerForParam ? ( -
- { - console.log("eo", emojiData) - interaction.setUserParam(emojiPickerForParam, emojiData.emoji) - setEmojiPickerForParam(null) - }} - /> -
- ) : ( -
{ - e.preventDefault() - handleSubmit() - }} - > - {params.map((param) => ( -
- {/* biome-ignore lint/a11y/noLabelWithoutControl: Nested correctly */} - -
- ))} - - -
- )} + return ( +
+ {emojiPickerForParam ? ( +
+ { + console.log("eo", emojiData); + interaction.setUserParam(emojiPickerForParam, emojiData.emoji); + setEmojiPickerForParam(null); + }} + />
- ) -} + ) : ( +
{ + e.preventDefault(); + handleSubmit(); + }} + > + {params.map((param) => ( +
+ {/* biome-ignore lint/a11y/noLabelWithoutControl: Nested correctly */} + +
+ ))} + + +
+ )} +
+ ); +}; diff --git a/packages/react/src/hooks/ChainProvider.tsx b/packages/react/src/hooks/ChainProvider.tsx index 20ad68b..fa6d558 100644 --- a/packages/react/src/hooks/ChainProvider.tsx +++ b/packages/react/src/hooks/ChainProvider.tsx @@ -1,14 +1,17 @@ -import { type ReactNode, Suspense } from "react" +import { type ReactNode, Suspense } from "react"; interface ChainProviderProps { - children: ReactNode - ProviderComponent: React.ComponentType<{ children: ReactNode }> + children: ReactNode; + ProviderComponent: React.ComponentType<{ children: ReactNode }>; } -export const ChainProvider = ({ children, ProviderComponent }: ChainProviderProps) => { - return ( - Loading...
}> - {children} - - ) -} +export const ChainProvider = ({ + children, + ProviderComponent, +}: ChainProviderProps) => { + return ( + Loading...}> + {children} + + ); +}; diff --git a/packages/react/src/hooks/PixelawProvider.tsx b/packages/react/src/hooks/PixelawProvider.tsx index 573ae03..bfdc590 100644 --- a/packages/react/src/hooks/PixelawProvider.tsx +++ b/packages/react/src/hooks/PixelawProvider.tsx @@ -1,132 +1,155 @@ import type { - Coordinate, - CoreDefaults, - CoreStatus, - Engine, - EngineConstructor, - Wallet, - WorldsRegistry, -} from "@pixelaw/core" -import { type BaseWallet, PixelawCore } from "@pixelaw/core" -import { createContext, type ReactNode, useContext, useEffect, useState } from "react" + Coordinate, + CoreDefaults, + CoreStatus, + Engine, + EngineConstructor, + Wallet, + WorldsRegistry, +} from "@pixelaw/core"; +import { type BaseWallet, PixelawCore } from "@pixelaw/core"; +import { + createContext, + type ReactNode, + useContext, + useEffect, + useState, +} from "react"; -import { createStorage, type StorageValue } from "unstorage" -import localStorageDriver from "unstorage/drivers/localstorage" +import { createStorage, type StorageValue } from "unstorage"; +import localStorageDriver from "unstorage/drivers/localstorage"; export type IPixelawContext = { - pixelawCore: PixelawCore - coreStatus: CoreStatus - wallet: BaseWallet | Wallet | null - app: string | null - engine: Engine | null - world: string | null - color: number - center: Coordinate - zoom: number - setWallet: (wallet: Wallet | null) => void - setApp: (app: string) => void - setWorld: (world: string) => void - setColor: (color: number) => void - setCenter: (center: Coordinate) => void - setZoom: (zoom: number) => void -} + pixelawCore: PixelawCore; + coreStatus: CoreStatus; + wallet: BaseWallet | Wallet | null; + app: string | null; + engine: Engine | null; + world: string | null; + color: number; + center: Coordinate; + zoom: number; + setWallet: (wallet: Wallet | null) => void; + setApp: (app: string) => void; + setWorld: (world: string) => void; + setColor: (color: number) => void; + setCenter: (center: Coordinate) => void; + setZoom: (zoom: number) => void; +}; -export const PixelawContext = createContext(undefined) +export const PixelawContext = createContext( + undefined, +); export const PixelawProvider = ({ - children, - worldsRegistry, - world, - engines, - coreDefaults, + children, + worldsRegistry, + world, + engines, + coreDefaults, }: { - children: ReactNode - worldsRegistry: WorldsRegistry - world: string - engines: Record> - coreDefaults?: CoreDefaults + children: ReactNode; + worldsRegistry: WorldsRegistry; + world: string; + engines: Record>; + coreDefaults?: CoreDefaults; }) => { - const [pixelawCore] = useState( - () => - new PixelawCore( - engines, - worldsRegistry, - createStorage({ - driver: localStorageDriver({}), - }), - ), - ) + const [pixelawCore] = useState( + () => + new PixelawCore( + engines, + worldsRegistry, + createStorage({ + driver: localStorageDriver({}), + }), + ), + ); - const [contextValues, setContextValues] = useState({ - pixelawCore, - coreStatus: "uninitialized", - wallet: pixelawCore.wallet, - app: pixelawCore.app, - engine: null, - world: pixelawCore.world, - color: pixelawCore.color, - center: pixelawCore.center, - zoom: pixelawCore.zoom, - setWallet: (wallet: Wallet | null) => { - pixelawCore.wallet = wallet - }, - setApp: (app: string) => { - pixelawCore.app = app - }, - setWorld: (world: string) => { - pixelawCore.loadWorld(world) - }, - setColor: (color: number) => { - pixelawCore.color = color - }, - setCenter: (center: Coordinate) => { - pixelawCore.center = center - }, - setZoom: (zoom: number) => { - pixelawCore.zoom = zoom - }, - }) + const [contextValues, setContextValues] = useState({ + pixelawCore, + coreStatus: "uninitialized", + wallet: pixelawCore.wallet, + app: pixelawCore.app, + engine: null, + world: pixelawCore.world, + color: pixelawCore.color, + center: pixelawCore.center, + zoom: pixelawCore.zoom, + setWallet: (wallet: Wallet | null) => { + pixelawCore.wallet = wallet; + }, + setApp: (app: string) => { + pixelawCore.app = app; + }, + setWorld: (world: string) => { + pixelawCore.loadWorld(world); + }, + setColor: (color: number) => { + pixelawCore.color = color; + }, + setCenter: (center: Coordinate) => { + pixelawCore.center = center; + }, + setZoom: (zoom: number) => { + pixelawCore.zoom = zoom; + }, + }); - useEffect(() => { - const handlers = { - statusChanged: (newStatus: CoreStatus) => setContextValues((prev) => ({ ...prev, coreStatus: newStatus })), - walletChanged: (newWallet: Wallet | null) => setContextValues((prev) => ({ ...prev, wallet: newWallet })), - appChanged: (newApp: string | null) => setContextValues((prev) => ({ ...prev, app: newApp })), - engineChanged: (newEngine: Engine | null) => setContextValues((prev) => ({ ...prev, engine: newEngine })), - worldChanged: (newWorld: string) => setContextValues((prev) => ({ ...prev, world: newWorld })), - colorChanged: (newColor: number) => setContextValues((prev) => ({ ...prev, color: newColor })), - centerChanged: (newCenter: Coordinate) => setContextValues((prev) => ({ ...prev, center: newCenter })), - zoomChanged: (newZoom: number) => setContextValues((prev) => ({ ...prev, zoom: newZoom })), - } + useEffect(() => { + const handlers = { + statusChanged: (newStatus: CoreStatus) => + setContextValues((prev) => ({ ...prev, coreStatus: newStatus })), + walletChanged: (newWallet: Wallet | null) => + setContextValues((prev) => ({ ...prev, wallet: newWallet })), + appChanged: (newApp: string | null) => + setContextValues((prev) => ({ ...prev, app: newApp })), + engineChanged: (newEngine: Engine | null) => + setContextValues((prev) => ({ ...prev, engine: newEngine })), + worldChanged: (newWorld: string) => + setContextValues((prev) => ({ ...prev, world: newWorld })), + colorChanged: (newColor: number) => + setContextValues((prev) => ({ ...prev, color: newColor })), + centerChanged: (newCenter: Coordinate) => + setContextValues((prev) => ({ ...prev, center: newCenter })), + zoomChanged: (newZoom: number) => + setContextValues((prev) => ({ ...prev, zoom: newZoom })), + }; - if (pixelawCore && pixelawCore.status === "uninitialized") { - pixelawCore.loadWorld(world, coreDefaults).catch((error) => { - console.error("Failed to load world:", error) - setContextValues((prev) => ({ - ...prev, - coreStatus: "error", - })) - }) - } + if (pixelawCore && pixelawCore.status === "uninitialized") { + pixelawCore.loadWorld(world, coreDefaults).catch((error) => { + console.error("Failed to load world:", error); + setContextValues((prev) => ({ + ...prev, + coreStatus: "error", + })); + }); + } - for (const [event, handler] of Object.entries(handlers)) { - // @ts-ignore - pixelawCore.events.on(event, handler) - } + for (const [event, handler] of Object.entries(handlers)) { + // @ts-ignore + pixelawCore.events.on(event, handler); + } - return () => { - for (const [event, handler] of Object.entries(handlers)) { - // @ts-ignore - pixelawCore.events.off(event, handler) - } - } - }, [pixelawCore, world, coreDefaults]) + return () => { + for (const [event, handler] of Object.entries(handlers)) { + // @ts-ignore + pixelawCore.events.off(event, handler); + } + }; + }, [pixelawCore, world, coreDefaults]); - return {children} -} + return ( + + {children} + + ); +}; export const usePixelawProvider = (): IPixelawContext => { - const context = useContext(PixelawContext) - if (!context) throw new Error("usePixelawProvider can only be used within a PixelawProvider") - return context -} + const context = useContext(PixelawContext); + if (!context) + throw new Error( + "usePixelawProvider can only be used within a PixelawProvider", + ); + return context; +}; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index c5ac869..a982d48 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,3 +1,3 @@ -export * from "./hooks/PixelawProvider" -export * from "./components/InteractionDialog" -export * from "./types" +export * from "./hooks/PixelawProvider"; +export * from "./components/InteractionDialog"; +export * from "./types"; diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index c10418b..24c8bc7 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -1,9 +1,9 @@ -import type { ReactNode } from "react" +import type { ReactNode } from "react"; export interface ChainProviderProps { - children: ReactNode + children: ReactNode; } export interface IChainProvider { - children: React.ReactNode + children: React.ReactNode; } diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index d8f3679..c04471d 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -1,7 +1,7 @@ -import { type Options, defineConfig } from "tsup" +import { type Options, defineConfig } from "tsup"; -import { tsupConfig } from "../../tsup.config" +import { tsupConfig } from "../../tsup.config"; export default defineConfig({ - ...(tsupConfig as Options), -}) + ...(tsupConfig as Options), +});