diff --git a/examples/example-vite-token-balance/.gitignore b/examples/example-vite-token-balance/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/example-vite-token-balance/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/example-vite-token-balance/README.md b/examples/example-vite-token-balance/README.md new file mode 100644 index 00000000..40ede56e --- /dev/null +++ b/examples/example-vite-token-balance/README.md @@ -0,0 +1,54 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config({ + extends: [ + // Remove ...tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config({ + plugins: { + // Add the react-x and react-dom plugins + 'react-x': reactX, + 'react-dom': reactDom, + }, + rules: { + // other rules... + // Enable its recommended typescript rules + ...reactX.configs['recommended-typescript'].rules, + ...reactDom.configs.recommended.rules, + }, +}) +``` diff --git a/examples/example-vite-token-balance/dojoConfig.ts b/examples/example-vite-token-balance/dojoConfig.ts new file mode 100644 index 00000000..e45ee85a --- /dev/null +++ b/examples/example-vite-token-balance/dojoConfig.ts @@ -0,0 +1,7 @@ +import { createDojoConfig } from "@dojoengine/core"; + +import manifest from "../../worlds/dojo-starter/manifest_dev.json"; + +export const dojoConfig = createDojoConfig({ + manifest, +}); diff --git a/examples/example-vite-token-balance/eslint.config.js b/examples/example-vite-token-balance/eslint.config.js new file mode 100644 index 00000000..092408a9 --- /dev/null +++ b/examples/example-vite-token-balance/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/examples/example-vite-token-balance/index.html b/examples/example-vite-token-balance/index.html new file mode 100644 index 00000000..2c65c8f1 --- /dev/null +++ b/examples/example-vite-token-balance/index.html @@ -0,0 +1,13 @@ + + + + + + + Example SDK Tokens + + +
+ + + diff --git a/examples/example-vite-token-balance/package.json b/examples/example-vite-token-balance/package.json new file mode 100644 index 00000000..bf089c1c --- /dev/null +++ b/examples/example-vite-token-balance/package.json @@ -0,0 +1,43 @@ +{ + "name": "example-vite-token-balance", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@cartridge/connector": "^0.7.6", + "@cartridge/controller": "^0.7.6", + "@dojoengine/core": "workspace:*", + "@dojoengine/predeployed-connector": "workspace:*", + "@dojoengine/sdk": "workspace:*", + "@dojoengine/torii-client": "workspace:*", + "@dojoengine/utils": "workspace:*", + "@starknet-react/chains": "catalog:", + "@starknet-react/core": "catalog:", + "@tailwindcss/vite": "^4.0.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "starknet": "catalog:", + "tailwindcss": "^4.0.14" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0", + "vite-plugin-top-level-await": "^1.5.0", + "vite-plugin-wasm": "^3.4.1" + } +} diff --git a/examples/example-vite-token-balance/public/vite.svg b/examples/example-vite-token-balance/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/examples/example-vite-token-balance/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/example-vite-token-balance/src/App.css b/examples/example-vite-token-balance/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/examples/example-vite-token-balance/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/example-vite-token-balance/src/App.tsx b/examples/example-vite-token-balance/src/App.tsx new file mode 100644 index 00000000..a008dbb1 --- /dev/null +++ b/examples/example-vite-token-balance/src/App.tsx @@ -0,0 +1,15 @@ +import "./App.css"; +import { Wallet } from "./components/wallet"; +import TokenBalance from "./components/token-balance"; + +function App() { + return ( + <> +

Connect your wallet

+ + + + ); +} + +export default App; diff --git a/examples/example-vite-token-balance/src/assets/react.svg b/examples/example-vite-token-balance/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/examples/example-vite-token-balance/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/example-vite-token-balance/src/components/starknet-provider.tsx b/examples/example-vite-token-balance/src/components/starknet-provider.tsx new file mode 100644 index 00000000..2aa3f629 --- /dev/null +++ b/examples/example-vite-token-balance/src/components/starknet-provider.tsx @@ -0,0 +1,41 @@ +import type { PropsWithChildren } from "react"; +import { mainnet } from "@starknet-react/chains"; +import { jsonRpcProvider, StarknetConfig, voyager } from "@starknet-react/core"; +import { dojoConfig } from "../../dojoConfig"; +import { usePredeployedAccounts } from "@dojoengine/predeployed-connector/react"; +import { ControllerConnector } from "@cartridge/connector"; +import { shortString } from "starknet"; + +const controller = new ControllerConnector({ + chains: [ + { + rpcUrl: "http://localhost:5050", + }, + ], + defaultChainId: shortString.encodeShortString("KATANA"), +}); + +export default function StarknetProvider({ children }: PropsWithChildren) { + const { connectors } = usePredeployedAccounts({ + rpc: dojoConfig.rpcUrl as string, + id: "katana", + name: "Katana", + }); + + const provider = jsonRpcProvider({ + rpc: () => ({ nodeUrl: dojoConfig.rpcUrl as string }), + }); + + return ( + + {/* @ts-ignore react version mismatch */} + {children} + + ); +} diff --git a/examples/example-vite-token-balance/src/components/token-balance.tsx b/examples/example-vite-token-balance/src/components/token-balance.tsx new file mode 100644 index 00000000..bae93c94 --- /dev/null +++ b/examples/example-vite-token-balance/src/components/token-balance.tsx @@ -0,0 +1,24 @@ +import { useTokens, WithAccount } from "@dojoengine/sdk/react"; + +function TokenBalance({ address }: { address: `0x${string}` }) { + const { tokens, getBalance, toDecimal } = useTokens({ + accountAddresses: [address], + }); + + return ( +
+ Token Balance: {address} +
+ {tokens.map((token, idx) => ( +
+ {token.symbol} +   + {toDecimal(token, getBalance(token))} +
+ ))} +
+
+ ); +} + +export default WithAccount(TokenBalance); diff --git a/examples/example-vite-token-balance/src/components/ui/button.tsx b/examples/example-vite-token-balance/src/components/ui/button.tsx new file mode 100644 index 00000000..e69de29b diff --git a/examples/example-vite-token-balance/src/components/wallet.tsx b/examples/example-vite-token-balance/src/components/wallet.tsx new file mode 100644 index 00000000..1adc6269 --- /dev/null +++ b/examples/example-vite-token-balance/src/components/wallet.tsx @@ -0,0 +1,68 @@ +import { + Connector, + useAccount, + useConnect, + useDisconnect, +} from "@starknet-react/core"; +import { useCallback, useState } from "react"; + +export function Wallet() { + const { connectAsync, connectors } = useConnect(); + const { address } = useAccount(); + const { disconnect } = useDisconnect(); + const [pendingConnectorId, setPendingConnectorId] = useState< + string | undefined + >(undefined); + + const connect = useCallback( + async (connector: Connector) => { + setPendingConnectorId(connector.id); + try { + await connectAsync({ connector }); + } catch (error) { + console.error(error); + } + setPendingConnectorId(undefined); + }, + [connectAsync] + ); + + function isWalletConnecting(connectorId: string) { + return pendingConnectorId === connectorId; + } + + if (address) { + return ( +
+
+ +
+
+ ); + } + + return ( +
+

Connect Wallet

+
+ {} + {connectors.map((connector) => ( + + ))} +
+
+ ); +} diff --git a/examples/example-vite-token-balance/src/index.css b/examples/example-vite-token-balance/src/index.css new file mode 100644 index 00000000..d6457264 --- /dev/null +++ b/examples/example-vite-token-balance/src/index.css @@ -0,0 +1,70 @@ +@import "tailwindcss"; + +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/example-vite-token-balance/src/main.tsx b/examples/example-vite-token-balance/src/main.tsx new file mode 100644 index 00000000..beff7e36 --- /dev/null +++ b/examples/example-vite-token-balance/src/main.tsx @@ -0,0 +1,38 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; + +import { init } from "@dojoengine/sdk"; +import { dojoConfig } from "../dojoConfig"; +import { DojoSdkProvider } from "@dojoengine/sdk/react"; +import { setupWorld } from "./typescript/contracts.gen"; +import StarknetProvider from "./components/starknet-provider.tsx"; + +async function main() { + const sdk = await init({ + client: { worldAddress: dojoConfig.manifest.world.address }, + domain: { + name: "WORLD_NAME", + version: "1.0", + chainId: "KATANA", + revision: "1", + }, + }); + + createRoot(document.getElementById("root")!).render( + + + + + + + + ); +} + +main().catch(console.error); diff --git a/examples/example-vite-token-balance/src/typescript/contracts.gen.ts b/examples/example-vite-token-balance/src/typescript/contracts.gen.ts new file mode 100644 index 00000000..aa4700cf --- /dev/null +++ b/examples/example-vite-token-balance/src/typescript/contracts.gen.ts @@ -0,0 +1,60 @@ +import type { DojoProvider, DojoCall } from "@dojoengine/core"; +import type { Account, AccountInterface, CairoCustomEnum } from "starknet"; + +export function setupWorld(provider: DojoProvider) { + const build_actions_move_calldata = ( + direction: CairoCustomEnum + ): DojoCall => { + return { + contractName: "actions", + entrypoint: "move", + calldata: [direction], + }; + }; + + const actions_move = async ( + snAccount: Account | AccountInterface, + direction: CairoCustomEnum + ) => { + try { + return await provider.execute( + snAccount, + build_actions_move_calldata(direction), + "dojo_starter" + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + const build_actions_spawn_calldata = (): DojoCall => { + return { + contractName: "actions", + entrypoint: "spawn", + calldata: [], + }; + }; + + const actions_spawn = async (snAccount: Account | AccountInterface) => { + try { + return await provider.execute( + snAccount, + build_actions_spawn_calldata(), + "dojo_starter" + ); + } catch (error) { + console.error(error); + throw error; + } + }; + + return { + actions: { + move: actions_move, + buildMoveCalldata: build_actions_move_calldata, + spawn: actions_spawn, + buildSpawnCalldata: build_actions_spawn_calldata, + }, + }; +} diff --git a/examples/example-vite-token-balance/src/typescript/models.gen.ts b/examples/example-vite-token-balance/src/typescript/models.gen.ts new file mode 100644 index 00000000..2162e0cc --- /dev/null +++ b/examples/example-vite-token-balance/src/typescript/models.gen.ts @@ -0,0 +1,148 @@ +import type { SchemaType as ISchemaType } from "@dojoengine/sdk"; + +import { CairoCustomEnum, CairoOption, CairoOptionVariant, BigNumberish } from 'starknet'; + +// Type definition for `dojo_starter::models::DirectionsAvailable` struct +export interface DirectionsAvailable { + player: string; + directions: Array; +} + +// Type definition for `dojo_starter::models::DirectionsAvailableValue` struct +export interface DirectionsAvailableValue { + directions: Array; +} + +// Type definition for `dojo_starter::models::Moves` struct +export interface Moves { + player: string; + remaining: BigNumberish; + last_direction: CairoOption; + can_move: boolean; +} + +// Type definition for `dojo_starter::models::MovesValue` struct +export interface MovesValue { + remaining: BigNumberish; + last_direction: CairoOption; + can_move: boolean; +} + +// Type definition for `dojo_starter::models::Position` struct +export interface Position { + player: string; + vec: Vec2; +} + +// Type definition for `dojo_starter::models::PositionValue` struct +export interface PositionValue { + vec: Vec2; +} + +// Type definition for `dojo_starter::models::Vec2` struct +export interface Vec2 { + x: BigNumberish; + y: BigNumberish; +} + +// Type definition for `dojo_starter::systems::actions::actions::Moved` struct +export interface Moved { + player: string; + direction: DirectionEnum; +} + +// Type definition for `dojo_starter::systems::actions::actions::MovedValue` struct +export interface MovedValue { + direction: DirectionEnum; +} + +// Type definition for `dojo_starter::models::Direction` enum +export type Direction = { + Left: string; + Right: string; + Up: string; + Down: string; +} +export type DirectionEnum = CairoCustomEnum; + +export interface SchemaType extends ISchemaType { + dojo_starter: { + DirectionsAvailable: DirectionsAvailable, + DirectionsAvailableValue: DirectionsAvailableValue, + Moves: Moves, + MovesValue: MovesValue, + Position: Position, + PositionValue: PositionValue, + Vec2: Vec2, + Moved: Moved, + MovedValue: MovedValue, + }, +} +export const schema: SchemaType = { + dojo_starter: { + DirectionsAvailable: { + player: "", + directions: [new CairoCustomEnum({ + Left: "", + Right: undefined, + Up: undefined, + Down: undefined, })], + }, + DirectionsAvailableValue: { + directions: [new CairoCustomEnum({ + Left: "", + Right: undefined, + Up: undefined, + Down: undefined, })], + }, + Moves: { + player: "", + remaining: 0, + last_direction: new CairoOption(CairoOptionVariant.None), + can_move: false, + }, + MovesValue: { + remaining: 0, + last_direction: new CairoOption(CairoOptionVariant.None), + can_move: false, + }, + Position: { + player: "", + vec: { x: 0, y: 0, }, + }, + PositionValue: { + vec: { x: 0, y: 0, }, + }, + Vec2: { + x: 0, + y: 0, + }, + Moved: { + player: "", + direction: new CairoCustomEnum({ + Left: "", + Right: undefined, + Up: undefined, + Down: undefined, }), + }, + MovedValue: { + direction: new CairoCustomEnum({ + Left: "", + Right: undefined, + Up: undefined, + Down: undefined, }), + }, + }, +}; +export enum ModelsMapping { + Direction = 'dojo_starter-Direction', + DirectionsAvailable = 'dojo_starter-DirectionsAvailable', + DirectionsAvailableValue = 'dojo_starter-DirectionsAvailableValue', + Moves = 'dojo_starter-Moves', + MovesValue = 'dojo_starter-MovesValue', + Position = 'dojo_starter-Position', + PositionValue = 'dojo_starter-PositionValue', + Vec2 = 'dojo_starter-Vec2', + Moved = 'dojo_starter-Moved', + MovedValue = 'dojo_starter-MovedValue', +} \ No newline at end of file diff --git a/examples/example-vite-token-balance/src/vite-env.d.ts b/examples/example-vite-token-balance/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/example-vite-token-balance/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/example-vite-token-balance/tsconfig.app.json b/examples/example-vite-token-balance/tsconfig.app.json new file mode 100644 index 00000000..358ca9ba --- /dev/null +++ b/examples/example-vite-token-balance/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/examples/example-vite-token-balance/tsconfig.json b/examples/example-vite-token-balance/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/examples/example-vite-token-balance/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/examples/example-vite-token-balance/tsconfig.node.json b/examples/example-vite-token-balance/tsconfig.node.json new file mode 100644 index 00000000..db0becc8 --- /dev/null +++ b/examples/example-vite-token-balance/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/example-vite-token-balance/vite.config.ts b/examples/example-vite-token-balance/vite.config.ts new file mode 100644 index 00000000..7b1e2d43 --- /dev/null +++ b/examples/example-vite-token-balance/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import wasm from "vite-plugin-wasm"; +import topLevelAwait from "vite-plugin-top-level-await"; +import tailwindcss from "@tailwindcss/vite"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), wasm(), topLevelAwait(), tailwindcss()], +}); diff --git a/package.json b/package.json index 046feb2d..3eb746cc 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "packageManager": "pnpm@10.5.2", "scripts": { "build:deps": "turbo build:deps", + "build:wasm": "turbo build:wasm", "build": "turbo build", "clean": "bash ./scripts/clean.sh", "prettier": "pnpx prettier --write packages examples", @@ -11,7 +12,7 @@ "format:check": "turbo format:check", "release": "pnpm build:deps && pnpm -F './packages/**' publish -r --force --no-git-checks", "release:dry-run": "pnpm -F './packages/**' publish -r --force --dry-run", - "dev": "turbo watch dev --concurrency 15", + "dev": "turbo watch dev --concurrency 20", "docs": "turbo run docs --ui stream" }, "devDependencies": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 73a744a5..f6f6f707 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -74,6 +74,8 @@ "@dojoengine/core": "workspace:*", "@dojoengine/torii-client": "workspace:*", "@dojoengine/utils": "workspace:*", + "@starknet-react/chains": "catalog:", + "@starknet-react/core": "catalog:", "immer": "^10.1.1", "zustand": "^4.5.6" }, diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 9b73de79..7d052846 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -3,6 +3,8 @@ import type { Account, Signature, StarknetDomain, TypedData } from "starknet"; import type { GetParams, + GetTokenBalanceRequest, + GetTokenRequest, SchemaType, SDK, SDKConfig, @@ -10,12 +12,22 @@ import type { SubscribeResponse, ToriiResponse, UnionOfModelData, + SubscribeTokenBalanceRequest, + UpdateTokenBalanceSubscriptionRequest, } from "./types"; + import { intoEntityKeysClause } from "./convertClauseToEntityKeysClause"; import { parseEntities } from "./parseEntities"; import { parseHistoricalEvents } from "./parseHistoricalEvents"; import type { ToriiQueryBuilder } from "./toriiQueryBuilder"; import { generateTypedData } from "./generateTypedData"; +import { + getTokens, + getTokenBalances, + onTokenBalanceUpdated, + updateTokenBalanceSubscription, + subscribeTokenBalance, +} from "./token"; export * from "./types"; export * from "./queryBuilder"; @@ -87,11 +99,15 @@ export async function init( const parsedData = parseEntities({ [entityId]: entityData, }); - callback({ data: parsedData }); + callback({ + data: parsedData, + error: undefined, + }); } } catch (error) { if (callback) { callback({ + data: undefined, error: error instanceof Error ? error @@ -153,11 +169,13 @@ export async function init( T, Historical >, + error: undefined, }); } } catch (error) { if (callback) { callback({ + data: undefined, error: error instanceof Error ? error @@ -169,6 +187,19 @@ export async function init( ), ]; }, + /** + * Subscribes to token balance updates + * + * # Parameters + * @param {SubscribeTokenBalanceRequest} request + * @returns {Promise<[torii.TokenBalances, torii.Subscription]>} + */ + subscribeTokenBalance: async ( + request: SubscribeTokenBalanceRequest + ): Promise<[torii.TokenBalances, torii.Subscription]> => { + return await subscribeTokenBalance(client, request); + }, + /** * Fetches entities based on the provided query. * @@ -251,42 +282,28 @@ export async function init( }, /** - * @param {(string)[]} contract_addresses - * @param {string[]} token_ids - * @returns {Promise} + * @param {GetTokenRequest} request + * @returns {Promise} */ - getTokens: async ( - contract_addresses: string[], - token_ids: string[] - ): Promise => { - return await client.getTokens(contract_addresses, token_ids); + getTokens: async (request: GetTokenRequest): Promise => { + return await getTokens(client, request); }, /** - * @param {(string)[]} contract_addresses - * @param {(string)[]} account_addresses - * @param {string[]} token_ids - * @returns {Promise} + * @param {GetTokenBalanceRequest} request + * @returns {Promise} */ getTokenBalances: async ( - contract_addresses: string[], - account_addresses: string[], - token_ids: string[] + request: GetTokenBalanceRequest ): Promise => { - return await client.getTokenBalances( - contract_addresses, - account_addresses, - token_ids - ); + return await getTokenBalances(client, request); }, /** * Subscribes to token balance updates * * # Parameters - * @param {string[]} contract_addresses - Array of contract addresses to filter (empty for all) - * @param {string[]} account_addresses - Array of account addresses to filter (empty for all) - * @param {string[]} token_ids - Array of token ids to filter (empty for all) + * @param {SubscribeTokenBalanceRequest} request * @param {Funtion} callback - JavaScript function to call on updates * * # Returns @@ -294,44 +311,25 @@ export async function init( * @returns torii.Subscription */ onTokenBalanceUpdated: ( - contract_addresses: string[], - account_addresses: string[], - token_ids: string[], - callback: Function + request: SubscribeTokenBalanceRequest ): torii.Subscription => { - return client.onTokenBalanceUpdated( - contract_addresses, - account_addresses, - token_ids, - callback - ); + return onTokenBalanceUpdated(client, request); }, /** * Updates an existing token balance subscription * * # Parameters - * @param {torii.Subscription} subscription - Existing subscription to update - * @param {string[]} contract_addresses - New array of contract addresses to filter - * @param {string[]} account_addresses - New array of account addresses to filter - * @param {string[]} token_ids - New array of token ids to filter (empty for all) + * @param {UpdateTokenBalanceSubscriptionRequest} request * * # Returns * Result containing unit or error * @returns {Promise} */ updateTokenBalanceSubscription: async ( - subscription: torii.Subscription, - contract_addresses: string[], - account_addresses: string[], - token_ids: string[] + request: UpdateTokenBalanceSubscriptionRequest ): Promise => { - return await client.updateTokenBalanceSubscription( - subscription, - contract_addresses, - account_addresses, - token_ids - ); + return await updateTokenBalanceSubscription(client, request); }, /** diff --git a/packages/sdk/src/react/hoc/with-account.tsx b/packages/sdk/src/react/hoc/with-account.tsx new file mode 100644 index 00000000..126f1fad --- /dev/null +++ b/packages/sdk/src/react/hoc/with-account.tsx @@ -0,0 +1,22 @@ +import type { AccountInterface } from "starknet"; +import { useAccount } from "@starknet-react/core"; + +interface WithAccountProps { + account: AccountInterface; + address: `0x${string}`; +} + +export function WithAccount

( + Component: React.ComponentType

, + Fallback: React.ComponentType = () =>

Please connect your wallet
+): React.FC> { + return (props) => { + const { account, address } = useAccount(); + if (!address) { + return Fallback ? : null; + } + const mergedProps = { ...props, account, address } as P & + WithAccountProps; + return ; + }; +} diff --git a/packages/sdk/src/react/hooks.ts b/packages/sdk/src/react/hooks.ts deleted file mode 100644 index 87e25c83..00000000 --- a/packages/sdk/src/react/hooks.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import type { BigNumberish } from "starknet"; -import type { - StandardizedQueryResult, - SubscribeResponse, - ToriiResponse, - SchemaType, - SubscribeParams, -} from "../types"; -import { DojoContext, type DojoContextType } from "./provider"; -import { create, type StoreApi, type UseBoundStore } from "zustand"; -import { createDojoStoreFactory } from "../state/zustand"; -import type { GameState } from "../state"; -import type { ToriiQueryBuilder } from "../toriiQueryBuilder"; -import type { EntityKeysClause, Subscription } from "@dojoengine/torii-client"; -import { getEntityIdFromKeys } from "@dojoengine/utils"; - -/** - * Factory function to create a React Zustand store based on a given SchemaType. - * - * @template T - The schema type. - * @returns A Zustand hook tailored to the provided schema. - */ -export function createDojoStore() { - // hacktually until I find a proper type input to createDojoStoreFactory - return createDojoStoreFactory(create) as unknown as UseBoundStore< - StoreApi> - >; -} - -/** - * Custom hook to retrieve a specific model for a given entityId within a specified namespace. - * - * @param entityId - The ID of the entity. - * @param model - The model to retrieve, specified as a string in the format "namespace-modelName". - * @returns The model structure if found, otherwise undefined. - */ -export function useModel< - N extends keyof SchemaType, - M extends keyof SchemaType[N] & string, - Client extends (...args: any) => any, - Schema extends SchemaType ->(entityId: BigNumberish, model: `${N}-${M}`): SchemaType[N][M] | undefined { - const [namespace, modelName] = model.split("-") as [N, M]; - const { useDojoStore } = - useContext>(DojoContext); - - // Select only the specific model data for the given entityId - const modelData = useDojoStore( - (state) => - state.entities[entityId.toString()]?.models?.[namespace]?.[ - modelName - ] as SchemaType[N][M] | undefined - ); - - return modelData; -} - -/** - * Custom hook to retrieve all entities that have a specific model. - * - * @param model - The model to retrieve, specified as a string in the format "namespace-modelName". - * @returns The model structure if found, otherwise undefined. - */ -export function useModels< - N extends keyof SchemaType, - M extends keyof SchemaType[N] & string, - Client extends (...args: any) => any, - Schema extends SchemaType ->(model: `${N}-${M}`): { [entityId: string]: SchemaType[N][M] | undefined } { - const [namespace, modelName] = model.split("-") as [N, M]; - const { useDojoStore } = - useContext>(DojoContext); - - const modelData = useDojoStore((state) => - state.getEntitiesByModel(namespace, modelName).map((entity) => ({ - [entity.entityId]: entity.models?.[namespace]?.[modelName], - })) - ) as unknown as { [entityId: string]: SchemaType[N][M] | undefined }; - - return modelData; -} - -/** - * Hook that exposes sdk features. - * - * @template Client Client function generated with `sozo build --typescript` - * @template Schema Schema function generated with `sozo build --typescript` - * @returns DojoContextType - */ -export function useDojoSDK< - Client extends (...args: any) => any, - Schema extends SchemaType ->(): DojoContextType { - return useContext>(DojoContext); -} - -/** - * If you know all distinct keys of your model, here is a way to compose it. - * - * @param keys Each keys corresponding to your model keys. - * @returns Composed entityId - */ -export function useEntityId(...keys: BigNumberish[]): BigNumberish { - const entityId = useMemo(() => { - if (keys.length > 0) { - return getEntityIdFromKeys(keys.map((k) => BigInt(k))); - } - return BigInt(0); - }, [keys]); - - return entityId; -} - -/** - * Base hook factory for creating subscription hooks with shared logic - */ -function createSubscriptionHook< - Schema extends SchemaType, - Historical extends boolean = false ->(config: { - subscribeMethod: ( - options: SubscribeParams - ) => Promise>; - updateSubscriptionMethod: ( - subscription: Subscription, - clause: any, - historical?: boolean - ) => Promise; - queryToHashedKeysMethod: ( - query: ToriiQueryBuilder, - historical?: boolean - ) => Promise<[ToriiResponse, EntityKeysClause[]]>; - processInitialData: (data: ToriiResponse) => void; - processUpdateData: (data: any) => void; - getErrorPrefix: () => string; - historical: Historical; -}) { - return function useSubscriptionHook(query: ToriiQueryBuilder) { - // Subscription handle to update - const subscriptionRef = useRef(null); - // Handle to user input query - const fetchingRef = useRef | null>(null); - // Async lock to sync with event loop - const isUpdating = useRef(false); - - const fetchData = useCallback(async () => { - // Wait until lock is released - while (isUpdating.current) { - await sleep(50); - } - - // Lock function - isUpdating.current = true; - - if (subscriptionRef.current) { - const [results, clause] = await config.queryToHashedKeysMethod( - fetchingRef.current!, - config.historical - ); - await config.updateSubscriptionMethod( - subscriptionRef.current, - clause, - config.historical - ); - config.processInitialData(results); - return null; - } - - const [initialData, subscription] = await config.subscribeMethod({ - query: fetchingRef.current!, - callback: ({ data, error }) => { - if (data) { - config.processUpdateData(data); - } - if (error) { - console.error( - `${config.getErrorPrefix()} - error subscribing with query: `, - query.toString() - ); - console.error(error); - } - }, - historical: config.historical, - }); - - config.processInitialData(initialData); - return subscription; - }, [query]); - - useEffect(() => { - if (!deepEqual(query, fetchingRef.current)) { - fetchingRef.current = query; - - fetchData() - .then((s) => { - if (s !== null) { - subscriptionRef.current = s; - } - }) - .catch((err) => { - console.error( - `${config.getErrorPrefix()} - error fetching data for query: `, - JSON.stringify(query) - ); - console.error(err); - }) - .finally(() => { - // Release lock - isUpdating.current = false; - }); - } - - return () => { - if (subscriptionRef.current) { - // subscriptionRef.current?.cancel(); - // subscriptionRef.current = null; - } - }; - }, [query, fetchData]); - }; -} - -/** - * Subscribe to entity changes. This hook fetches initial data from torii and subscribes to each entity change. - * Use `useModel` to access your data. - * - * @param query ToriiQuery - */ -export function useEntityQuery( - query: ToriiQueryBuilder -) { - const { sdk, useDojoStore } = useDojoSDK<() => any, Schema>(); - const state = useDojoStore((s) => s); - - const useEntityQueryHook = createSubscriptionHook({ - subscribeMethod: (options) => sdk.subscribeEntityQuery(options), - updateSubscriptionMethod: (subscription, clause) => - sdk.updateEntitySubscription(subscription, clause), - queryToHashedKeysMethod: (query) => sdk.toriiQueryIntoHashedKeys(query), - processInitialData: (data) => state.mergeEntities(data), - processUpdateData: (data) => { - const entity = data.pop(); - - if (entity && entity.entityId !== "0x0") { - state.updateEntity(entity); - } - }, - getErrorPrefix: () => "Dojo.js - useEntityQuery", - historical: false, - }); - - useEntityQueryHook(query); -} - -/** - * Subscribe to event changes. This hook fetches initial events from torii and subscribes to new events. - * - * @param query ToriiQuery - */ -export function useEventQuery( - query: ToriiQueryBuilder -) { - const { sdk, useDojoStore } = useDojoSDK<() => any, Schema>(); - const state = useDojoStore((s) => s); - - const useEventQueryHook = createSubscriptionHook({ - subscribeMethod: (options) => sdk.subscribeEventQuery(options), - updateSubscriptionMethod: (subscription, clause) => - sdk.updateEventMessageSubscription(subscription, clause, false), - queryToHashedKeysMethod: (query) => - sdk.toriiEventMessagesQueryIntoHashedKeys(query, false), - processInitialData: (data) => state.mergeEntities(data), - processUpdateData: (data) => { - const event = data.pop(); - if (event && event.entityId !== "0x0") { - state.updateEntity(event); - } - }, - getErrorPrefix: () => "Dojo.js - useEventQuery", - historical: false, - }); - - useEventQueryHook(query); -} - -/** - * Subscribe to historical events changes. This hook fetches initial data from torii and subscribes to entity changes. - * You need to specify to torii which events has to be taken in account as historical events. - * - * @param query ToriiQuery - */ -export function useHistoricalEventsQuery( - query: ToriiQueryBuilder -) { - const { sdk } = useDojoSDK<() => any, Schema>(); - const [events, setEvents] = useState[]>([]); - - const useHistoricalEventsQueryHook = createSubscriptionHook({ - subscribeMethod: (options) => sdk.subscribeEventQuery(options), - updateSubscriptionMethod: (subscription, clause) => - sdk.updateEventMessageSubscription(subscription, clause, true), - queryToHashedKeysMethod: (query) => - sdk.toriiEventMessagesQueryIntoHashedKeys(query, true), - processInitialData: (data) => setEvents(data), - processUpdateData: (data) => { - const event = data.pop(); - if (event) { - setEvents((ev) => [event, ...ev]); - } - }, - getErrorPrefix: () => "Dojo.js - useHistoricalEventsQuery", - historical: true, - }); - - useHistoricalEventsQueryHook(query); - - return events; -} - -/** - * Performs a deep comparison between two values to determine if they are equivalent. - * @param a First value to compare - * @param b Second value to compare - * @returns True if the values are equivalent, false otherwise - */ -function deepEqual(a: any, b: any): boolean { - // If the values are strictly equal, return true - if (a === b) return true; - - // If either value is null or not an object, they're not equal - if ( - a === null || - b === null || - typeof a !== "object" || - typeof b !== "object" - ) { - return false; - } - - // Handle arrays - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false; - - for (let i = 0; i < a.length; i++) { - if (!deepEqual(a[i], b[i])) return false; - } - - return true; - } - - // Handle Date objects - if (a instanceof Date && b instanceof Date) { - return a.getTime() === b.getTime(); - } - - // Handle regular expressions - if (a instanceof RegExp && b instanceof RegExp) { - return a.toString() === b.toString(); - } - - // Get all keys from both objects - const keysA = Object.keys(a); - const keysB = Object.keys(b); - - // If number of keys is different, objects are not equal - if (keysA.length !== keysB.length) return false; - - // Check if every key in a exists in b and has the same value - return keysA.every( - (key) => - Object.prototype.hasOwnProperty.call(b, key) && - deepEqual(a[key], b[key]) - ); -} - -/** - * Creates a Promise that resolves after the specified time - * @param ms The time to sleep in milliseconds - * @returns A Promise that resolves after the specified time - */ -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/packages/sdk/src/react/hooks/entities.ts b/packages/sdk/src/react/hooks/entities.ts new file mode 100644 index 00000000..2884eaa1 --- /dev/null +++ b/packages/sdk/src/react/hooks/entities.ts @@ -0,0 +1,35 @@ +import { useDojoSDK } from "../hooks"; +import { createSubscriptionHook } from "./hooks"; +import type { SchemaType, ToriiQueryBuilder } from "../../types"; + +/** + * Subscribe to entity changes. This hook fetches initial data from torii and subscribes to each entity change. + * Use `useModel` to access your data. + * + * @param query ToriiQuery + */ +export function useEntityQuery( + query: ToriiQueryBuilder +) { + const { sdk, useDojoStore } = useDojoSDK<() => any, Schema>(); + const state = useDojoStore((s) => s); + + const useEntityQueryHook = createSubscriptionHook({ + subscribeMethod: (options) => sdk.subscribeEntityQuery(options), + updateSubscriptionMethod: (subscription, clause) => + sdk.updateEntitySubscription(subscription, clause), + queryToHashedKeysMethod: (query) => sdk.toriiQueryIntoHashedKeys(query), + processInitialData: (data) => state.mergeEntities(data), + processUpdateData: (data) => { + const entity = data.pop(); + + if (entity && entity.entityId !== "0x0") { + state.updateEntity(entity); + } + }, + getErrorPrefix: () => "Dojo.js - useEntityQuery", + historical: false, + }); + + useEntityQueryHook(query); +} diff --git a/packages/sdk/src/react/hooks/events.ts b/packages/sdk/src/react/hooks/events.ts new file mode 100644 index 00000000..c99e33fe --- /dev/null +++ b/packages/sdk/src/react/hooks/events.ts @@ -0,0 +1,70 @@ +import { useDojoSDK } from "../hooks"; +import { createSubscriptionHook } from "./hooks"; +import type { SchemaType, ToriiQueryBuilder } from "../../types"; +import { useState } from "react"; +import type { StandardizedQueryResult } from "../../types"; + +/** + * Subscribe to event changes. This hook fetches initial events from torii and subscribes to new events. + * + * @param query ToriiQuery + */ +export function useEventQuery( + query: ToriiQueryBuilder +) { + const { sdk, useDojoStore } = useDojoSDK<() => any, Schema>(); + const state = useDojoStore((s) => s); + + const useEventQueryHook = createSubscriptionHook({ + subscribeMethod: (options) => sdk.subscribeEventQuery(options), + updateSubscriptionMethod: (subscription, clause) => + sdk.updateEventMessageSubscription(subscription, clause, false), + queryToHashedKeysMethod: (query) => + sdk.toriiEventMessagesQueryIntoHashedKeys(query, false), + processInitialData: (data) => state.mergeEntities(data), + processUpdateData: (data) => { + const event = data.pop(); + if (event && event.entityId !== "0x0") { + state.updateEntity(event); + } + }, + getErrorPrefix: () => "Dojo.js - useEventQuery", + historical: false, + }); + + useEventQueryHook(query); +} + +/** + * Subscribe to historical events changes. This hook fetches initial data from torii and subscribes to entity changes. + * You need to specify to torii which events has to be taken in account as historical events. + * + * @param query ToriiQuery + */ +export function useHistoricalEventsQuery( + query: ToriiQueryBuilder +) { + const { sdk } = useDojoSDK<() => any, Schema>(); + const [events, setEvents] = useState[]>([]); + + const useHistoricalEventsQueryHook = createSubscriptionHook({ + subscribeMethod: (options) => sdk.subscribeEventQuery(options), + updateSubscriptionMethod: (subscription, clause) => + sdk.updateEventMessageSubscription(subscription, clause, true), + queryToHashedKeysMethod: (query) => + sdk.toriiEventMessagesQueryIntoHashedKeys(query, true), + processInitialData: (data) => setEvents(data), + processUpdateData: (data) => { + const event = data.pop(); + if (event) { + setEvents((ev) => [event, ...ev]); + } + }, + getErrorPrefix: () => "Dojo.js - useHistoricalEventsQuery", + historical: true, + }); + + useHistoricalEventsQueryHook(query); + + return events; +} diff --git a/packages/sdk/src/react/hooks/hooks.ts b/packages/sdk/src/react/hooks/hooks.ts new file mode 100644 index 00000000..8803a13c --- /dev/null +++ b/packages/sdk/src/react/hooks/hooks.ts @@ -0,0 +1,119 @@ +import { useCallback, useEffect, useRef } from "react"; +import type { + SubscribeResponse, + ToriiResponse, + SchemaType, + SubscribeParams, +} from "../../types"; +import type { ToriiQueryBuilder } from "../../toriiQueryBuilder"; +import type { EntityKeysClause, Subscription } from "@dojoengine/torii-client"; +import { deepEqual, sleep } from "./utils"; + +/** + * Base hook factory for creating subscription hooks with shared logic + */ +export function createSubscriptionHook< + Schema extends SchemaType, + Historical extends boolean = false, +>(config: { + subscribeMethod: ( + options: SubscribeParams + ) => Promise>; + updateSubscriptionMethod: ( + subscription: Subscription, + clause: any, + historical?: boolean + ) => Promise; + queryToHashedKeysMethod: ( + query: ToriiQueryBuilder, + historical?: boolean + ) => Promise<[ToriiResponse, EntityKeysClause[]]>; + processInitialData: (data: ToriiResponse) => void; + processUpdateData: (data: any) => void; + getErrorPrefix: () => string; + historical: Historical; +}) { + return function useSubscriptionHook(query: ToriiQueryBuilder) { + // Subscription handle to update + const subscriptionRef = useRef(null); + // Handle to user input query + const fetchingRef = useRef | null>(null); + // Async lock to sync with event loop + const isUpdating = useRef(false); + + const fetchData = useCallback(async () => { + // Wait until lock is released + while (isUpdating.current) { + await sleep(50); + } + + // Lock function + isUpdating.current = true; + + if (subscriptionRef.current) { + const [results, clause] = await config.queryToHashedKeysMethod( + fetchingRef.current!, + config.historical + ); + await config.updateSubscriptionMethod( + subscriptionRef.current, + clause, + config.historical + ); + config.processInitialData(results); + return null; + } + + const [initialData, subscription] = await config.subscribeMethod({ + query: fetchingRef.current!, + callback: ({ data, error }) => { + if (data) { + config.processUpdateData(data); + } + if (error) { + console.error( + `${config.getErrorPrefix()} - error subscribing with query: `, + query.toString() + ); + console.error(error); + } + }, + historical: config.historical, + }); + + config.processInitialData(initialData); + return subscription; + }, [query]); + + useEffect(() => { + if (!deepEqual(query, fetchingRef.current)) { + fetchingRef.current = query; + + fetchData() + .then((s) => { + if (s !== null) { + subscriptionRef.current = s; + } + }) + .catch((err) => { + console.error( + `${config.getErrorPrefix()} - error fetching data for query: `, + JSON.stringify(query) + ); + console.error(err); + }) + .finally(() => { + // Release lock + isUpdating.current = false; + }); + } + + return () => { + if (subscriptionRef.current) { + // subscriptionRef.current?.cancel(); + // subscriptionRef.current = null; + } + }; + }, [query, fetchData]); + }; +} diff --git a/packages/sdk/src/react/hooks/index.ts b/packages/sdk/src/react/hooks/index.ts new file mode 100644 index 00000000..a512c54c --- /dev/null +++ b/packages/sdk/src/react/hooks/index.ts @@ -0,0 +1,41 @@ +import { useContext, useMemo } from "react"; +import type { BigNumberish } from "starknet"; +import type { SchemaType } from "../../types"; +import { DojoContext, type DojoContextType } from "../provider"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; + +export * from "./entities"; +export * from "./events"; +export * from "./token"; +export * from "./state"; + +/** + * Hook that exposes sdk features. + * + * @template Client Client function generated with `sozo build --typescript` + * @template Schema Schema function generated with `sozo build --typescript` + * @returns DojoContextType + */ +export function useDojoSDK< + Client extends (...args: any) => any, + Schema extends SchemaType, +>(): DojoContextType { + return useContext>(DojoContext); +} + +/** + * If you know all distinct keys of your model, here is a way to compose it. + * + * @param keys Each keys corresponding to your model keys. + * @returns Composed entityId + */ +export function useEntityId(...keys: BigNumberish[]): BigNumberish { + const entityId = useMemo(() => { + if (keys.length > 0) { + return getEntityIdFromKeys(keys.map((k) => BigInt(k))); + } + return BigInt(0); + }, [keys]); + + return entityId; +} diff --git a/packages/sdk/src/react/hooks/state.ts b/packages/sdk/src/react/hooks/state.ts new file mode 100644 index 00000000..752d7a1d --- /dev/null +++ b/packages/sdk/src/react/hooks/state.ts @@ -0,0 +1,73 @@ +import { useContext } from "react"; +import type { BigNumberish } from "starknet"; +import type { SchemaType } from "../../types"; +import { DojoContext, type DojoContextType } from "../provider"; +import { create, type StoreApi, type UseBoundStore } from "zustand"; +import { createDojoStoreFactory } from "../../state/zustand"; +import type { GameState } from "../../state"; + +/** + * Factory function to create a React Zustand store based on a given SchemaType. + * + * @template T - The schema type. + * @returns A Zustand hook tailored to the provided schema. + */ +export function createDojoStore() { + // hacktually until I find a proper type input to createDojoStoreFactory + return createDojoStoreFactory(create) as unknown as UseBoundStore< + StoreApi> + >; +} + +/** + * Custom hook to retrieve a specific model for a given entityId within a specified namespace. + * + * @param entityId - The ID of the entity. + * @param model - The model to retrieve, specified as a string in the format "namespace-modelName". + * @returns The model structure if found, otherwise undefined. + */ +export function useModel< + N extends keyof SchemaType, + M extends keyof SchemaType[N] & string, + Client extends (...args: any) => any, + Schema extends SchemaType, +>(entityId: BigNumberish, model: `${N}-${M}`): SchemaType[N][M] | undefined { + const [namespace, modelName] = model.split("-") as [N, M]; + const { useDojoStore } = + useContext>(DojoContext); + + // Select only the specific model data for the given entityId + const modelData = useDojoStore( + (state) => + state.entities[entityId.toString()]?.models?.[namespace]?.[ + modelName + ] as SchemaType[N][M] | undefined + ); + + return modelData; +} + +/** + * Custom hook to retrieve all entities that have a specific model. + * + * @param model - The model to retrieve, specified as a string in the format "namespace-modelName". + * @returns The model structure if found, otherwise undefined. + */ +export function useModels< + N extends keyof SchemaType, + M extends keyof SchemaType[N] & string, + Client extends (...args: any) => any, + Schema extends SchemaType, +>(model: `${N}-${M}`): { [entityId: string]: SchemaType[N][M] | undefined } { + const [namespace, modelName] = model.split("-") as [N, M]; + const { useDojoStore } = + useContext>(DojoContext); + + const modelData = useDojoStore((state) => + state.getEntitiesByModel(namespace, modelName).map((entity) => ({ + [entity.entityId]: entity.models?.[namespace]?.[modelName], + })) + ) as unknown as { [entityId: string]: SchemaType[N][M] | undefined }; + + return modelData; +} diff --git a/packages/sdk/src/react/hooks/token.ts b/packages/sdk/src/react/hooks/token.ts new file mode 100644 index 00000000..47e42df4 --- /dev/null +++ b/packages/sdk/src/react/hooks/token.ts @@ -0,0 +1,126 @@ +import { useDojoSDK } from "../hooks"; +import { useState, useEffect, useRef, useCallback } from "react"; +import type { + Token, + Tokens, + TokenBalance, + TokenBalances, + Subscription, +} from "@dojoengine/torii-client"; +import type { + GetTokenRequest, + GetTokenBalanceRequest, + SubscriptionCallbackArgs, +} from "../.."; +import { deepEqual } from "./utils"; + +export function useTokens(request: GetTokenRequest & GetTokenBalanceRequest) { + const { sdk } = useDojoSDK(); + const [tokens, setTokens] = useState([]); + const requestRef = useRef(null); + const [tokenBalances, setTokenBalances] = useState([]); + const subscriptionRef = useRef(null); + + const fetchTokens = useCallback(async () => { + const tokens = await sdk.getTokens({ + contractAddresses: request.contractAddresses ?? [], + tokenIds: request.tokenIds ?? [], + }); + console.log("tokens", tokens); + setTokens(tokens); + }, [sdk, request]); + + const fetchTokenBalances = useCallback(async () => { + const [tokenBalances, subscription] = await sdk.subscribeTokenBalance({ + contractAddresses: request.contractAddresses ?? [], + accountAddresses: request.accountAddresses ?? [], + tokenIds: request.tokenIds ?? [], + callback: ({ + data, + error, + }: SubscriptionCallbackArgs) => { + if (error) { + console.error(error); + return; + } + setTokenBalances((prev) => updateTokenBalancesList(prev, data)); + }, + }); + console.log("tokenBalances", tokenBalances); + subscriptionRef.current = subscription; + setTokenBalances(tokenBalances); + }, [sdk, request]); + + useEffect(() => { + if (!deepEqual(request, requestRef.current)) { + requestRef.current = request; + fetchTokens(); + fetchTokenBalances(); + } + }, [request]); + + function getBalance(token: Token): TokenBalance | undefined { + return tokenBalances.find( + (balance) => balance.contract_address === token.contract_address + ); + } + + function toDecimal( + token: Token, + balance: TokenBalance | undefined + ): number { + return ( + Number.parseInt(balance?.balance ?? "0", 16) * 10 ** -token.decimals + ); + } + + return { tokens, balances: tokenBalances, getBalance, toDecimal }; +} + +function updateTokenBalancesList( + previousBalances: TokenBalance[], + newBalance: TokenBalance +): TokenBalance[] { + const existingBalanceIndex = previousBalances.findIndex( + (balance) => balance.token_id === newBalance.token_id + ); + + // If balance doesn't exist, append it to the list + if (existingBalanceIndex === -1) { + return [...previousBalances, newBalance]; + } + + // If balance exists, update it while preserving order + return previousBalances.map((balance, index) => + index === existingBalanceIndex ? newBalance : balance + ); +} + +// /** +// * Subscribe to event changes. This hook fetches initial events from torii and subscribes to new events. +// * +// * @param query ToriiQuery +// */ +// export function useTokenBalances(request: GetTokenBalanceRequest) { +// const { sdk, useDojoStore } = useDojoSDK(); +// const state = useDojoStore((s) => s); + +// const useEventQueryHook = createSubscriptionHook({ +// subscribeMethod: (request) => sdk.onTokenBalanceUpdated(request), +// updateSubscriptionMethod: (subscription, clause) => +// sdk.updateEventMessageSubscription(subscription, clause, false), +// queryToHashedKeysMethod: (query) => +// sdk.toriiEventMessagesQueryIntoHashedKeys(query, false), +// processInitialData: (data) => state.mergeEntities(data), +// processUpdateData: (data) => { +// const event = data.pop(); +// if (event && event.entityId !== "0x0") { +// state.updateEntity(event); +// } +// }, +// getErrorPrefix: () => "Dojo.js - useTokenBalances", +// historical: false, +// }); + +// useEventQueryHook(query); +// } diff --git a/packages/sdk/src/react/hooks/utils.ts b/packages/sdk/src/react/hooks/utils.ts new file mode 100644 index 00000000..053a1bcc --- /dev/null +++ b/packages/sdk/src/react/hooks/utils.ts @@ -0,0 +1,64 @@ +/** + * Performs a deep comparison between two values to determine if they are equivalent. + * @param a First value to compare + * @param b Second value to compare + * @returns True if the values are equivalent, false otherwise + */ +export function deepEqual(a: any, b: any): boolean { + // If the values are strictly equal, return true + if (a === b) return true; + + // If either value is null or not an object, they're not equal + if ( + a === null || + b === null || + typeof a !== "object" || + typeof b !== "object" + ) { + return false; + } + + // Handle arrays + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) return false; + } + + return true; + } + + // Handle Date objects + if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime(); + } + + // Handle regular expressions + if (a instanceof RegExp && b instanceof RegExp) { + return a.toString() === b.toString(); + } + + // Get all keys from both objects + const keysA = Object.keys(a); + const keysB = Object.keys(b); + + // If number of keys is different, objects are not equal + if (keysA.length !== keysB.length) return false; + + // Check if every key in a exists in b and has the same value + return keysA.every( + (key) => + Object.prototype.hasOwnProperty.call(b, key) && + deepEqual(a[key], b[key]) + ); +} + +/** + * Creates a Promise that resolves after the specified time + * @param ms The time to sleep in milliseconds + * @returns A Promise that resolves after the specified time + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/sdk/src/react/index.ts b/packages/sdk/src/react/index.ts index cdd2a909..f0630210 100644 --- a/packages/sdk/src/react/index.ts +++ b/packages/sdk/src/react/index.ts @@ -1,2 +1,3 @@ export * from "./hooks"; export * from "./provider"; +export * from "./hoc/with-account"; diff --git a/packages/sdk/src/react/provider.tsx b/packages/sdk/src/react/provider.tsx index af9f752f..b39885e9 100644 --- a/packages/sdk/src/react/provider.tsx +++ b/packages/sdk/src/react/provider.tsx @@ -1,8 +1,8 @@ -import { ReactNode, useContext, createContext } from "react"; -import { SchemaType, SDK } from "../types"; -import { DojoConfig, DojoProvider } from "@dojoengine/core"; -import { createDojoStore } from "./hooks"; -import { GameState } from "../state"; +import { type ReactNode, useContext, createContext } from "react"; +import type { SchemaType, SDK } from "../types"; +import { type DojoConfig, DojoProvider } from "@dojoengine/core"; +import { createDojoStore } from "./hooks/state"; +import type { GameState } from "../state"; // Define the hook type export type DojoStoreHook = ( @@ -15,7 +15,7 @@ export type DojoStoreHook = ( */ export interface DojoContextType< Client extends (...args: any) => any, - Schema extends SchemaType + Schema extends SchemaType, > { /** The Dojo client instance */ config: DojoConfig; diff --git a/packages/sdk/src/token.ts b/packages/sdk/src/token.ts new file mode 100644 index 00000000..810f313b --- /dev/null +++ b/packages/sdk/src/token.ts @@ -0,0 +1,161 @@ +import type * as torii from "@dojoengine/torii-client"; +import type { + GetTokenBalanceRequest, + GetTokenRequest, + SubscribeTokenBalanceRequest, + UpdateTokenBalanceSubscriptionRequest, +} from "./types"; +import { addAddressPadding } from "starknet"; + +export const defaultTokenBalance: torii.TokenBalance = { + balance: + "0x0000000000000000000000000000000000000000000000000000000000000000", + account_address: "0x0", + contract_address: "0x0", + token_id: + "0x0000000000000000000000000000000000000000000000000000000000000000", +}; + +function parseTokenRequest( + req: T +): T { + if (req.contractAddresses) { + req.contractAddresses = req.contractAddresses.map((r) => + addAddressPadding(r) + ); + } + + if (req.accountAddresses) { + req.accountAddresses = req.accountAddresses.map((r) => + addAddressPadding(r) + ); + } + + return req; +} +/** + * @param {GetTokenRequest} request + * @returns {Promise} + */ +export async function getTokens( + client: torii.ToriiClient, + request: GetTokenRequest +): Promise { + const { contractAddresses, tokenIds } = parseTokenRequest(request); + return await client.getTokens(contractAddresses ?? [], tokenIds ?? []); +} + +/** + * @param {GetTokenBalanceRequest} request + * @returns {Promise} + */ +export async function getTokenBalances( + client: torii.ToriiClient, + request: GetTokenBalanceRequest +): Promise { + const { contractAddresses, accountAddresses, tokenIds } = + parseTokenRequest(request); + return await client.getTokenBalances( + contractAddresses ?? [], + accountAddresses ?? [], + tokenIds ?? [] + ); +} + +/** + * Subscribes to token balance updates + * + * # Parameters + * @param {SubscribeTokenBalanceRequest} request + * + * # Returns + * Result containing subscription handle or error + * @returns torii.Subscription + */ +export function onTokenBalanceUpdated( + client: torii.ToriiClient, + request: SubscribeTokenBalanceRequest +): torii.Subscription { + const { contractAddresses, accountAddresses, tokenIds, callback } = + parseTokenRequest(request); + return client.onTokenBalanceUpdated( + contractAddresses ?? [], + accountAddresses ?? [], + tokenIds ?? [], + callback + ); +} + +/** + * Updates an existing token balance subscription + * + * # Parameters + * @param {torii.Subscription} subscription - Existing subscription to update + * @param {UpdateTokenBalanceSubscriptionRequest} request + * + * # Returns + * Result containing unit or error + * @returns {Promise} + */ +export async function updateTokenBalanceSubscription( + client: torii.ToriiClient, + request: UpdateTokenBalanceSubscriptionRequest +): Promise { + const { subscription, contractAddresses, accountAddresses, tokenIds } = + request; + return await client.updateTokenBalanceSubscription( + subscription, + contractAddresses ?? [], + accountAddresses ?? [], + tokenIds ?? [] + ); +} +/** + * Subscribes to token balance updates and returns initial data with subscription + * + * # Parameters + * @param {SubscribeTokenBalanceRequest} request - Request parameters + * + * # Returns + * Array containing initial token balances and subscription handle + * @returns {Promise<[torii.TokenBalances, torii.Subscription]>} + */ +export async function subscribeTokenBalance( + client: torii.ToriiClient, + request: SubscribeTokenBalanceRequest +): Promise<[torii.TokenBalances, torii.Subscription]> { + const { contractAddresses, accountAddresses, tokenIds, callback } = request; + + // Get initial token balances + const initialBalances = await getTokenBalances(client, { + contractAddresses: contractAddresses ?? [], + accountAddresses: accountAddresses ?? [], + tokenIds: tokenIds ?? [], + }); + + // Create subscription for updates + const subscription = client.onTokenBalanceUpdated( + contractAddresses ?? [], + accountAddresses ?? [], + tokenIds ?? [], + (res: torii.TokenBalance) => { + if (res === defaultTokenBalance) { + return; + } + try { + callback({ + data: res, + error: undefined, + }); + return; + } catch (error) { + callback({ + data: undefined, + error: error as Error, + }); + } + } + ); + + return [initialBalances, subscription]; +} diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index c59f468d..0fc56dd2 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -248,16 +248,50 @@ export type UnionOfModelData = { export type ToriiResponse< T extends SchemaType, - Historical extends boolean + Historical extends boolean, > = Historical extends true ? StandardizedQueryResult[] : StandardizedQueryResult; export type SubscribeResponse< T extends SchemaType, - Historical extends boolean + Historical extends boolean, > = [ToriiResponse, torii.Subscription]; +export interface GetTokenRequest { + contractAddresses?: string[]; + tokenIds?: string[]; +} + +export interface GetTokenBalanceRequest extends GetTokenRequest { + accountAddresses?: string[]; +} + +type Success = { + data: T; + error: undefined; +}; + +type Failure = { + data: undefined; + error: E; +}; + +export type SubscriptionCallbackArgs = Success | Failure; + +// ToriiResponse +export type SubscriptionCallback = ( + response: SubscriptionCallbackArgs +) => void; + +export type SubscribeTokenBalanceRequest = GetTokenBalanceRequest & { + callback: SubscriptionCallback; +}; + +export type UpdateTokenBalanceSubscriptionRequest = GetTokenBalanceRequest & { + subscription: torii.Subscription; +}; + /** * SDK interface for interacting with the DojoEngine. * @@ -293,6 +327,17 @@ export interface SDK { params: SubscribeParams ) => Promise>; + /** + * Subscribes to token balance updates + * + * # Parameters + * @param {SubscribeTokenBalanceRequest} request + * @returns {Promise<[torii.TokenBalances, torii.Subscription]>} + */ + subscribeTokenBalance: ( + request: SubscribeTokenBalanceRequest + ) => Promise<[torii.TokenBalances, torii.Subscription]>; + /** * Fetches entities from the Torii client based on the provided query. * @@ -328,10 +373,7 @@ export interface SDK { * @param {string[]} token_ids * @returns {Promise} */ - getTokens( - contract_addresses: string[], - token_ids: string[] - ): Promise; + getTokens(request: GetTokenRequest): Promise; /** * @param {string[]} account_addresses @@ -340,9 +382,7 @@ export interface SDK { * @returns {Promise} */ getTokenBalances( - contract_addresses: string[], - account_addresses: string[], - token_ids: string[] + request: GetTokenBalanceRequest ): Promise; /** @@ -359,10 +399,7 @@ export interface SDK { * @returns torii.Subscription */ onTokenBalanceUpdated: ( - contract_addresses: string[], - account_addresses: string[], - token_ids: string[], - callback: Function + request: SubscribeTokenBalanceRequest ) => torii.Subscription; /** @@ -379,10 +416,7 @@ export interface SDK { * @returns {Promise} */ updateTokenBalanceSubscription: ( - subscription: torii.Subscription, - contract_addresses: string[], - account_addresses: string[], - token_ids: string[] + request: UpdateTokenBalanceSubscriptionRequest ) => Promise; /** @@ -489,25 +523,23 @@ export interface SDKFunctionOptions { export interface SubscribeParams< T extends SchemaType, - Historical extends boolean = false + Historical extends boolean = false, > { // Query object used to filter entities. query: ToriiQueryBuilder; // The callback function to handle the response. - callback: (response: { - data?: ToriiResponse; - error?: Error; - }) => void; + callback: SubscriptionCallback>; // historical events historical?: Historical; } export interface GetParams< T extends SchemaType, - Historical extends boolean = false + Historical extends boolean = false, > { // The query object used to filter entities. query: ToriiQueryBuilder; // historical events historical?: Historical; } +export { ToriiQueryBuilder }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4da0e8c4..cb39db84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ catalogs: version: 18.3.1 starknet: specifier: 6.23.1 - version: 6.21.0 + version: 6.23.1 importers: @@ -162,7 +162,7 @@ importers: version: 3.1.0 '@starknet-react/core': specifier: 'catalog:' - version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.3) + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) react: specifier: 'catalog:' version: 18.3.1 @@ -171,7 +171,7 @@ importers: version: 18.3.1(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) devDependencies: '@eslint/js': specifier: ^9.21.0 @@ -238,7 +238,7 @@ importers: version: 3.60.0-beta.14 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) devDependencies: typescript: specifier: ^5.6.2 @@ -266,7 +266,7 @@ importers: version: 11.11.1 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) devDependencies: '@types/highlight.js': specifier: ^10.1.0 @@ -400,7 +400,7 @@ importers: version: 3.1.0 '@starknet-react/core': specifier: 'catalog:' - version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.8.2) + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.8.2) '@t3-oss/env-core': specifier: ^0.11.1 version: 0.11.1(typescript@5.8.2)(zod@3.24.2) @@ -436,7 +436,7 @@ importers: version: 7.54.2(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -515,7 +515,7 @@ importers: version: 3.87.0 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) zustand: specifier: ^4.5.5 version: 4.5.5(@types/react@19.0.10)(immer@10.1.1)(react@19.0.0) @@ -585,7 +585,7 @@ importers: version: 7.8.1 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) vite-plugin-wasm: specifier: ^3.3.0 version: 3.4.1(vite@4.5.5(@types/node@20.17.10)(lightningcss@1.29.1)(terser@5.39.0)) @@ -712,7 +712,7 @@ importers: version: 4.0.3 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) styled-components: specifier: ^6.1.14 version: 6.1.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -833,7 +833,7 @@ importers: version: 7.8.1 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) vite-plugin-top-level-await: specifier: ^1.4.4 version: 1.4.4(@swc/helpers@0.5.5)(rollup@2.79.2)(vite@4.5.5(@types/node@20.17.10)(lightningcss@1.29.1)(terser@5.39.0)) @@ -918,7 +918,7 @@ importers: version: 3.1.0 '@starknet-react/core': specifier: 'catalog:' - version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.3) + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) '@types/uuid': specifier: ^10.0.0 version: 10.0.0 @@ -933,7 +933,7 @@ importers: version: 18.3.1(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) uuid: specifier: ^10.0.0 version: 10.0.0 @@ -1030,7 +1030,7 @@ importers: version: 3.1.0 '@starknet-react/core': specifier: 'catalog:' - version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.3) + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) '@tanstack/react-query': specifier: ^5.64.1 version: 5.64.1(react@18.3.1) @@ -1066,7 +1066,7 @@ importers: version: 18.3.1(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -1235,7 +1235,7 @@ importers: version: 7.8.1 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) tailwind-merge: specifier: ^2.5.2 version: 2.6.0 @@ -1350,7 +1350,7 @@ importers: version: 2.2.14 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) vite-plugin-wasm: specifier: ^3.3.0 version: 3.4.1(vite@5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0)) @@ -1380,6 +1380,91 @@ importers: specifier: ^1.4.4 version: 1.4.4(@swc/helpers@0.5.5)(rollup@4.37.0)(vite@5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0)) + examples/example-vite-token-balance: + dependencies: + '@cartridge/connector': + specifier: ^0.7.6 + version: 0.7.6(@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@19.0.0)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3))(open@8.4.2)(starknet@6.23.1(encoding@0.1.13)) + '@cartridge/controller': + specifier: ^0.7.6 + version: 0.7.6(open@8.4.2)(starknet@6.23.1(encoding@0.1.13)) + '@dojoengine/core': + specifier: workspace:* + version: link:../../packages/core + '@dojoengine/predeployed-connector': + specifier: workspace:* + version: link:../../packages/predeployed-connector + '@dojoengine/sdk': + specifier: workspace:* + version: link:../../packages/sdk + '@dojoengine/torii-client': + specifier: workspace:* + version: link:../../packages/torii-client + '@dojoengine/utils': + specifier: workspace:* + version: link:../../packages/utils + '@starknet-react/chains': + specifier: 'catalog:' + version: 3.1.0 + '@starknet-react/core': + specifier: 'catalog:' + version: 3.6.2(get-starknet-core@4.0.0)(react@19.0.0)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) + '@tailwindcss/vite': + specifier: ^4.0.1 + version: 4.0.1(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + starknet: + specifier: 'catalog:' + version: 6.23.1(encoding@0.1.13) + tailwindcss: + specifier: ^4.0.14 + version: 4.0.14 + devDependencies: + '@eslint/js': + specifier: ^9.21.0 + version: 9.21.0 + '@types/react': + specifier: ^19.0.10 + version: 19.0.10 + '@types/react-dom': + specifier: ^19.0.4 + version: 19.0.4(@types/react@19.0.10) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)) + eslint: + specifier: ^9.21.0 + version: 9.21.0(jiti@2.4.2) + eslint-plugin-react-hooks: + specifier: ^5.1.0 + version: 5.2.0(eslint@9.21.0(jiti@2.4.2)) + eslint-plugin-react-refresh: + specifier: ^0.4.19 + version: 0.4.19(eslint@9.21.0(jiti@2.4.2)) + globals: + specifier: ^15.15.0 + version: 15.15.0 + typescript: + specifier: ~5.7.2 + version: 5.7.3 + typescript-eslint: + specifier: ^8.24.1 + version: 8.26.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3) + vite: + specifier: ^6.2.0 + version: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + vite-plugin-top-level-await: + specifier: ^1.5.0 + version: 1.5.0(@swc/helpers@0.5.5)(rollup@4.37.0)(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)) + vite-plugin-wasm: + specifier: ^3.4.1 + version: 3.4.1(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)) + examples/example-vue-app-recs: dependencies: '@dojoengine/core': @@ -1408,7 +1493,7 @@ importers: version: 2.2.14 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) vite-plugin-wasm: specifier: ^3.3.0 version: 3.4.1(vite@5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0)) @@ -1439,7 +1524,7 @@ importers: version: 2.0.13(typescript@5.7.2)(zod@3.24.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) zod: specifier: ^3.23.8 version: 3.24.1 @@ -1473,7 +1558,7 @@ importers: version: 1.6.0 '@starknet-react/core': specifier: 'catalog:' - version: 3.6.2(get-starknet-core@4.0.0)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.2) + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.2) encoding: specifier: ^0.1.13 version: 0.1.13 @@ -1491,7 +1576,7 @@ importers: version: 18.3.1(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) devDependencies: '@babel/core': specifier: ^7.25.2 @@ -1501,10 +1586,10 @@ importers: version: 7.26.0(@babel/core@7.26.0) '@testing-library/react': specifier: ^16.0.1 - version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18) + version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@testing-library/react-hooks': specifier: ^8.0.1 - version: 8.0.1(@types/react@18.3.18) + version: 8.0.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/js-cookie': specifier: ^3.0.6 version: 3.0.6 @@ -1595,7 +1680,7 @@ importers: version: 0.7.10 '@starknet-react/core': specifier: 'catalog:' - version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.3) + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) react: specifier: 'catalog:' version: 18.3.1 @@ -1604,7 +1689,7 @@ importers: version: 18.3.1(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) devDependencies: '@rollup/plugin-commonjs': specifier: ^28.0.0 @@ -1674,7 +1759,7 @@ importers: version: 7.5.5 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) type-fest: specifier: ^2.14.0 version: 2.19.0 @@ -1721,30 +1806,36 @@ importers: '@dojoengine/utils': specifier: workspace:* version: link:../utils + '@starknet-react/chains': + specifier: 'catalog:' + version: 3.1.0 + '@starknet-react/core': + specifier: 'catalog:' + version: 3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) '@tanstack/react-query': specifier: ^5.62.16 - version: 5.66.9(react@19.0.0) + version: 5.66.9(react@18.3.1) '@types/react': specifier: 'catalog:' - version: 19.0.10 + version: 18.3.18 '@types/react-dom': specifier: 'catalog:' - version: 18.3.5(@types/react@19.0.10) + version: 18.3.5(@types/react@18.3.18) immer: specifier: ^10.1.1 version: 10.1.1 react: specifier: 'catalog:' - version: 19.0.0 + version: 18.3.1 react-dom: specifier: 'catalog:' - version: 18.3.1(react@19.0.0) + version: 18.3.1(react@18.3.1) starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) zustand: specifier: ^4.5.6 - version: 4.5.6(@types/react@19.0.10)(immer@10.1.1)(react@19.0.0) + version: 4.5.6(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1) devDependencies: '@rollup/plugin-commonjs': specifier: ^28.0.2 @@ -1790,7 +1881,7 @@ importers: version: link:../torii-client starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@22.13.13)(jsdom@24.1.3)(lightningcss@1.29.1)(terser@5.39.0) @@ -1843,7 +1934,7 @@ importers: version: 0.2.3 starknet: specifier: 'catalog:' - version: 6.21.0(encoding@0.1.13) + version: 6.23.1(encoding@0.1.13) devDependencies: '@types/elliptic': specifier: ^6.4.18 @@ -2653,6 +2744,53 @@ packages: '@canvas/image-data@1.0.0': resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==} + '@cartridge/account-wasm@0.7.6': + resolution: {integrity: sha512-uulPdfiPesJ5pSg7wZPOH+0ppTQ87Zv16oFipimwlQbZ+uoDX9FoAWNIHQrfCQfOnl43IHTLwYEa1Bo92p5eKQ==} + + '@cartridge/connector@0.7.6': + resolution: {integrity: sha512-yo7JxSQUi0ukg83db3MO00WdwUaMiIQvx/mi+UK4kTgIdlNvZcqzprz9kta3h/p3dzWIMxkd9zi8P4TAGKK4Sw==} + peerDependencies: + '@starknet-react/core': ^3.7 + + '@cartridge/controller@0.7.6': + resolution: {integrity: sha512-izpepJGDlSndZmutTTulThDbT2ccpNhb7TGj7guT5XKdxQzjrwvKQNIeQqmZd7n3TTWcfp8+Z2F3fw8aRyruPw==} + peerDependencies: + open: ^10.1.0 + starknet: ^6.21.0 + + '@cartridge/penpal@6.2.4': + resolution: {integrity: sha512-tdpOnSJJBFMlgLZ1+z9Ho5e6cG5EgMAb1Cmmh1lGT2tmplogU/XPMjLE6CwvKAPDoe6a38iMnbH+ySTAWWIOKA==} + + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': + resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} + cpu: [arm64] + os: [darwin] + + '@cbor-extract/cbor-extract-darwin-x64@2.2.0': + resolution: {integrity: sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==} + cpu: [x64] + os: [darwin] + + '@cbor-extract/cbor-extract-linux-arm64@2.2.0': + resolution: {integrity: sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==} + cpu: [arm64] + os: [linux] + + '@cbor-extract/cbor-extract-linux-arm@2.2.0': + resolution: {integrity: sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==} + cpu: [arm] + os: [linux] + + '@cbor-extract/cbor-extract-linux-x64@2.2.0': + resolution: {integrity: sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==} + cpu: [x64] + os: [linux] + + '@cbor-extract/cbor-extract-win32-x64@2.2.0': + resolution: {integrity: sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==} + cpu: [x64] + os: [win32] + '@changesets/apply-release-plan@7.0.7': resolution: {integrity: sha512-qnPOcmmmnD0MfMg9DjU1/onORFyRpDXkMMl2IJg9mECY6RnxL3wN0TCCc92b2sXt1jt8DgjAUUsZYGUGTdYIXA==} @@ -7435,6 +7573,27 @@ packages: resolution: {integrity: sha512-UsLBb+ALvxbRTYMlx3WJ36oq13Ps4n8tcN8biFrtiCbA8TiS0sgSAOr0lPQpzQqZuVSjscPjX43ciKf33hvkQw==} engines: {node: '>=12'} + '@telegram-apps/bridge@1.9.2': + resolution: {integrity: sha512-SJLcNWLXhbbZr9MiqFH/g2ceuitSJKMxUIZysK4zUNyTUNuonrQG80Q/yrO+XiNbKUj8WdDNM86NBARhuyyinQ==} + + '@telegram-apps/navigation@1.0.13': + resolution: {integrity: sha512-TsUueB5LQp77GQHoMa93nq26Uw7GJjrFCPbyseMVU7aBBxAc+8CV2IYytRwcVp5sv/q7ThK5X4JaKn2V1yBHDQ==} + + '@telegram-apps/sdk@2.11.3': + resolution: {integrity: sha512-KdULzgRe1gcR8B3Z/t3hQrEaDmLGrfsL2IePtPP6ehtMn5tT0uPfnjtDLjDNQMyI7D4Tv2ZOzvDx45wOhhreXg==} + + '@telegram-apps/signals@1.1.1': + resolution: {integrity: sha512-vz37r8lemGpPzDiBRfqpXYBynzmy3SFnY6zfHsTZABTYYt0b0WQZyU5mFDqqqugGhka78Gy11xmr9csgy4YgGA==} + + '@telegram-apps/toolkit@1.1.1': + resolution: {integrity: sha512-+vhKx6ngfvjyTE6Xagl3z1TPVbfx5s7xAkcYzCdHYUo6T60jLIqLgyZMcI1UPoIAMuMu1pHoO+p8QNCj/+tFmw==} + + '@telegram-apps/transformers@1.2.2': + resolution: {integrity: sha512-vvMwXckd1D7Ozc0h66PSUwF5QLrRV9HlGJFFeBuUex8QEk5mSPtsJkLiqB8aBbwuFDa91+TUSM/CxqPZO/e9YQ==} + + '@telegram-apps/types@1.2.1': + resolution: {integrity: sha512-so4HLh7clur0YyMthi9KVIgWoGpZdXlFOuQjk3+Q5NAvJZ11nAheBSwPlGw/Ko92+zwvrSBE/lQyN2+p17RP+w==} + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -7705,6 +7864,11 @@ packages: peerDependencies: '@types/react': ^18.0.0 + '@types/react-dom@19.0.4': + resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} + peerDependencies: + '@types/react': ^19.0.0 + '@types/react-reconciler@0.26.7': resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==} @@ -8733,6 +8897,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64url@3.0.1: + resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} + engines: {node: '>=6.0.0'} + bech32@1.1.4: resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} @@ -8928,6 +9096,13 @@ packages: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true + cbor-extract@2.2.0: + resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} + hasBin: true + + cbor-x@1.6.0: + resolution: {integrity: sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -11948,7 +12123,6 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lie@3.3.0: @@ -12628,6 +12802,10 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} + hasBin: true + node-gyp@10.3.1: resolution: {integrity: sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==} engines: {node: ^16.14.0 || >=18.0.0} @@ -13464,6 +13642,11 @@ packages: peerDependencies: react: ^18.3.1 + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + react-element-to-jsx-string@15.0.0: resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} peerDependencies: @@ -13880,6 +14063,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} @@ -14140,8 +14326,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - starknet@6.21.0: - resolution: {integrity: sha512-xUSlqyE+J/S5un3TyQY0Kehilh1u7ewPaut87eOxTDS1r90SU0QvQ3JEECp5LbW/sqsaMhfb+tGTBGrKzXg7bg==} + starknet@6.23.1: + resolution: {integrity: sha512-vQV9luXpmwZZs9RVZaRwm2iD8T0PYx1AzgZeQsCvD89tR0HwUF0paty27ZzuJrdPe0CmAs/ipAYFCE55jbj0RQ==} stats-gl@2.4.2: resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} @@ -14408,6 +14594,9 @@ packages: tailwindcss@4.0.1: resolution: {integrity: sha512-UK5Biiit/e+r3i0O223bisoS5+y7ZT1PM8Ojn0MxRHzXN1VPZ2KY6Lo6fhu1dOfCfyUAlK7Lt6wSxowRabATBw==} + tailwindcss@4.0.14: + resolution: {integrity: sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw==} + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -17675,6 +17864,48 @@ snapshots: '@canvas/image-data@1.0.0': {} + '@cartridge/account-wasm@0.7.6': {} + + '@cartridge/connector@0.7.6(@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@19.0.0)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3))(open@8.4.2)(starknet@6.23.1(encoding@0.1.13))': + dependencies: + '@cartridge/controller': 0.7.6(open@8.4.2)(starknet@6.23.1(encoding@0.1.13)) + '@starknet-react/core': 3.6.2(get-starknet-core@4.0.0)(react@19.0.0)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3) + transitivePeerDependencies: + - open + - starknet + + '@cartridge/controller@0.7.6(open@8.4.2)(starknet@6.23.1(encoding@0.1.13))': + dependencies: + '@cartridge/account-wasm': 0.7.6 + '@cartridge/penpal': 6.2.4 + '@starknet-io/types-js': 0.7.10 + '@telegram-apps/sdk': 2.11.3 + base64url: 3.0.1 + cbor-x: 1.6.0 + fast-deep-equal: 3.1.3 + open: 8.4.2 + starknet: 6.23.1(encoding@0.1.13) + + '@cartridge/penpal@6.2.4': {} + + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-darwin-x64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-linux-arm64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-linux-arm@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-linux-x64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-win32-x64@2.2.0': + optional: true + '@changesets/apply-release-plan@7.0.7': dependencies: '@changesets/config': 3.0.5 @@ -20514,7 +20745,7 @@ snapshots: '@octokit/request-error': 3.0.3 '@octokit/types': 9.3.2 is-plain-object: 5.0.0 - node-fetch: 2.6.7(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) universal-user-agent: 6.0.1 transitivePeerDependencies: - encoding @@ -21989,7 +22220,7 @@ snapshots: '@scure/bip32@1.3.2': dependencies: '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.9 '@scure/bip32@1.5.0': @@ -22012,7 +22243,7 @@ snapshots: '@scure/bip39@1.2.1': dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.9 '@scure/bip39@1.4.0': @@ -22028,7 +22259,7 @@ snapshots: '@scure/starknet@1.1.0': dependencies: '@noble/curves': 1.7.0 - '@noble/hashes': 1.6.0 + '@noble/hashes': 1.6.1 '@shikijs/core@1.27.0': dependencies: @@ -22111,7 +22342,23 @@ snapshots: '@starknet-react/chains@3.1.0': {} - '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.3)': + '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.2)': + dependencies: + '@starknet-io/types-js': 0.7.10 + '@starknet-react/chains': 3.1.0 + '@tanstack/react-query': 5.69.0(react@18.3.1) + eventemitter3: 5.0.1 + get-starknet-core: 4.0.0 + react: 18.3.1 + starknet: 6.23.1(encoding@0.1.13) + viem: 2.23.15(typescript@5.7.2)(zod@3.24.2) + zod: 3.24.2 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + + '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3)': dependencies: '@starknet-io/types-js': 0.7.10 '@starknet-react/chains': 3.1.0 @@ -22119,7 +22366,7 @@ snapshots: eventemitter3: 5.0.1 get-starknet-core: 4.0.0 react: 18.3.1 - starknet: 6.21.0(encoding@0.1.13) + starknet: 6.23.1(encoding@0.1.13) viem: 2.23.15(typescript@5.7.3)(zod@3.24.2) zod: 3.24.2 transitivePeerDependencies: @@ -22127,7 +22374,7 @@ snapshots: - typescript - utf-8-validate - '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.21.0(encoding@0.1.13))(typescript@5.8.2)': + '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@18.3.1)(starknet@6.23.1(encoding@0.1.13))(typescript@5.8.2)': dependencies: '@starknet-io/types-js': 0.7.10 '@starknet-react/chains': 3.1.0 @@ -22135,7 +22382,7 @@ snapshots: eventemitter3: 5.0.1 get-starknet-core: 4.0.0 react: 18.3.1 - starknet: 6.21.0(encoding@0.1.13) + starknet: 6.23.1(encoding@0.1.13) viem: 2.23.15(typescript@5.8.2)(zod@3.24.2) zod: 3.24.2 transitivePeerDependencies: @@ -22143,15 +22390,16 @@ snapshots: - typescript - utf-8-validate - '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(starknet@6.21.0(encoding@0.1.13))(typescript@5.7.2)': + '@starknet-react/core@3.6.2(get-starknet-core@4.0.0)(react@19.0.0)(starknet@6.23.1(encoding@0.1.13))(typescript@5.7.3)': dependencies: '@starknet-io/types-js': 0.7.10 '@starknet-react/chains': 3.1.0 - '@tanstack/react-query': 5.69.0(react@18.3.1) + '@tanstack/react-query': 5.69.0(react@19.0.0) eventemitter3: 5.0.1 get-starknet-core: 4.0.0 - starknet: 6.21.0(encoding@0.1.13) - viem: 2.23.15(typescript@5.7.2)(zod@3.24.2) + react: 19.0.0 + starknet: 6.23.1(encoding@0.1.13) + viem: 2.23.15(typescript@5.7.3)(zod@3.24.2) zod: 3.24.2 transitivePeerDependencies: - bufferutil @@ -23042,6 +23290,14 @@ snapshots: tailwindcss: 4.0.1 vite: 6.0.11(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + '@tailwindcss/vite@4.0.1(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0))': + dependencies: + '@tailwindcss/node': 4.0.1 + '@tailwindcss/oxide': 4.0.1 + lightningcss: 1.29.1 + tailwindcss: 4.0.1 + vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + '@tanstack/eslint-plugin-query@5.62.16(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/utils': 8.20.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.7.3) @@ -23063,16 +23319,21 @@ snapshots: '@tanstack/query-core': 5.64.1 react: 18.3.1 - '@tanstack/react-query@5.66.9(react@19.0.0)': + '@tanstack/react-query@5.66.9(react@18.3.1)': dependencies: '@tanstack/query-core': 5.66.4 - react: 19.0.0 + react: 18.3.1 '@tanstack/react-query@5.69.0(react@18.3.1)': dependencies: '@tanstack/query-core': 5.69.0 react: 18.3.1 + '@tanstack/react-query@5.69.0(react@19.0.0)': + dependencies: + '@tanstack/query-core': 5.69.0 + react: 19.0.0 + '@tanstack/react-router@1.97.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/history': 1.97.0 @@ -23142,6 +23403,38 @@ snapshots: '@tanstack/virtual-file-routes@1.97.0': {} + '@telegram-apps/bridge@1.9.2': + dependencies: + '@telegram-apps/signals': 1.1.1 + '@telegram-apps/toolkit': 1.1.1 + '@telegram-apps/transformers': 1.2.2 + '@telegram-apps/types': 1.2.1 + + '@telegram-apps/navigation@1.0.13': + dependencies: + '@telegram-apps/bridge': 1.9.2 + '@telegram-apps/signals': 1.1.1 + '@telegram-apps/toolkit': 1.1.1 + + '@telegram-apps/sdk@2.11.3': + dependencies: + '@telegram-apps/bridge': 1.9.2 + '@telegram-apps/navigation': 1.0.13 + '@telegram-apps/signals': 1.1.1 + '@telegram-apps/toolkit': 1.1.1 + '@telegram-apps/transformers': 1.2.2 + + '@telegram-apps/signals@1.1.1': {} + + '@telegram-apps/toolkit@1.1.1': {} + + '@telegram-apps/transformers@1.2.2': + dependencies: + '@telegram-apps/toolkit': 1.1.1 + '@telegram-apps/types': 1.2.1 + + '@telegram-apps/types@1.2.1': {} + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 @@ -23174,17 +23467,21 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react-hooks@8.0.1(@types/react@18.3.18)': + '@testing-library/react-hooks@8.0.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - react-error-boundary: 3.1.4 + react: 18.3.1 + react-error-boundary: 3.1.4(react@18.3.1) optionalDependencies: '@types/react': 18.3.18 + react-dom: 18.3.1(react@18.3.1) - '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)': + '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) @@ -23420,7 +23717,7 @@ snapshots: dependencies: '@types/react': 18.3.18 - '@types/react-dom@18.3.5(@types/react@19.0.10)': + '@types/react-dom@19.0.4(@types/react@19.0.10)': dependencies: '@types/react': 19.0.10 @@ -24169,6 +24466,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-react@4.3.4(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0))': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0))(vue@3.5.13(typescript@5.7.2))': dependencies: vite: 5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0) @@ -24993,6 +25301,8 @@ snapshots: base64-js@1.5.1: {} + base64url@3.0.1: {} + bech32@1.1.4: {} before-after-hook@2.2.3: {} @@ -25223,6 +25533,22 @@ snapshots: ansicolors: 0.3.2 redeyed: 2.1.1 + cbor-extract@2.2.0: + dependencies: + node-gyp-build-optional-packages: 5.1.1 + optionalDependencies: + '@cbor-extract/cbor-extract-darwin-arm64': 2.2.0 + '@cbor-extract/cbor-extract-darwin-x64': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm64': 2.2.0 + '@cbor-extract/cbor-extract-linux-x64': 2.2.0 + '@cbor-extract/cbor-extract-win32-x64': 2.2.0 + optional: true + + cbor-x@1.6.0: + optionalDependencies: + cbor-extract: 2.2.0 + ccount@2.0.1: {} chai@4.5.0: @@ -29286,6 +29612,11 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-gyp-build-optional-packages@5.1.1: + dependencies: + detect-libc: 2.0.3 + optional: true + node-gyp@10.3.1: dependencies: env-paths: 2.2.1 @@ -30253,11 +30584,10 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-dom@18.3.1(react@19.0.0): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 react: 19.0.0 - scheduler: 0.23.2 + scheduler: 0.25.0 react-element-to-jsx-string@15.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -30267,9 +30597,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 - react-error-boundary@3.1.4: + react-error-boundary@3.1.4(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 + react: 18.3.1 react-hook-form@7.54.2(react@18.3.1): dependencies: @@ -30832,6 +31163,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + scheduler@0.25.0: {} + scrypt-js@3.0.1: {} scuid@1.1.0: {} @@ -31139,7 +31472,7 @@ snapshots: stackback@0.0.2: {} - starknet@6.21.0(encoding@0.1.13): + starknet@6.23.1(encoding@0.1.13): dependencies: '@noble/curves': 1.7.0 '@noble/hashes': 1.6.0 @@ -31466,6 +31799,8 @@ snapshots: tailwindcss@4.0.1: {} + tailwindcss@4.0.14: {} + tapable@2.2.1: {} tar-fs@2.1.1: @@ -31851,7 +32186,7 @@ snapshots: tunnel-rat@0.1.2(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1): dependencies: - zustand: 4.5.5(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1) + zustand: 4.5.6(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer @@ -32185,10 +32520,6 @@ snapshots: dependencies: react: 18.3.1 - use-sync-external-store@1.4.0(react@19.0.0): - dependencies: - react: 19.0.0 - util-deprecate@1.0.2: {} util@0.10.4: @@ -32562,6 +32893,16 @@ snapshots: - '@swc/helpers' - rollup + vite-plugin-top-level-await@1.5.0(@swc/helpers@0.5.5)(rollup@4.37.0)(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)): + dependencies: + '@rollup/plugin-virtual': 3.0.2(rollup@4.37.0) + '@swc/core': 1.11.13(@swc/helpers@0.5.5) + uuid: 10.0.0 + vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + transitivePeerDependencies: + - '@swc/helpers' + - rollup + vite-plugin-wasm@3.4.1(vite@3.2.11(@types/node@22.13.13)(terser@5.39.0)): dependencies: vite: 3.2.11(@types/node@22.13.13)(terser@5.39.0) @@ -32602,6 +32943,10 @@ snapshots: dependencies: vite: 6.2.3(@types/node@20.17.27)(jiti@1.21.7)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + vite-plugin-wasm@3.4.1(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)): + dependencies: + vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0) + vite-preset-react@2.3.0(vite@6.2.3(@types/node@20.17.27)(jiti@1.21.7)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0)): dependencies: '@vitejs/plugin-react': 1.3.2 @@ -32770,6 +33115,20 @@ snapshots: tsx: 4.19.2 yaml: 2.7.0 + vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.0): + dependencies: + esbuild: 0.25.1 + postcss: 8.5.3 + rollup: 4.37.0 + optionalDependencies: + '@types/node': 22.13.13 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.1 + terser: 5.39.0 + tsx: 4.19.2 + yaml: 2.7.0 + vitefu@0.2.5(vite@5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0)): optionalDependencies: vite: 5.4.11(@types/node@22.13.13)(lightningcss@1.29.1)(terser@5.39.0) @@ -32970,8 +33329,8 @@ snapshots: webauthn-p256@0.0.10: dependencies: - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 webgl-constants@1.1.1: {} @@ -33366,14 +33725,6 @@ snapshots: immer: 10.1.1 react: 18.3.1 - zustand@4.5.6(@types/react@19.0.10)(immer@10.1.1)(react@19.0.0): - dependencies: - use-sync-external-store: 1.4.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - immer: 10.1.1 - react: 19.0.0 - zustand@5.0.3(@types/react@18.3.18)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.18