Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fix react-sdk example + predeployed connector + historical events #367

Merged
merged 3 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/example-vite-react-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
"dependencies": {
"@dojoengine/core": "workspace:*",
"@dojoengine/create-burner": "workspace:*",
"@dojoengine/predeployed-connector": "workspace:*",
"@dojoengine/sdk": "workspace:*",
"@dojoengine/torii-client": "workspace:*",
"@dojoengine/torii-wasm": "workspace:*",
"@dojoengine/utils": "workspace:*",
"@starknet-react/chains": "catalog:",
"@starknet-react/core": "catalog:",
"@types/uuid": "^10.0.0",
"immer": "^10.1.1",
"react": "^18.3.1",
Expand Down
161 changes: 76 additions & 85 deletions examples/example-vite-react-sdk/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,111 @@
import { useEffect, useMemo } from "react";
import { QueryBuilder, SDK, createDojoStore } from "@dojoengine/sdk";
import {
ParsedEntity,
QueryBuilder,
SDK,
createDojoStore,
} from "@dojoengine/sdk";
import { getEntityIdFromKeys } from "@dojoengine/utils";
import { addAddressPadding } from "starknet";
import { AccountInterface, addAddressPadding } from "starknet";

import { Models, Schema } from "./bindings.ts";
import {
Direction,
ModelsMapping,
SchemaType,
} from "./typescript/models.gen.ts";
import { useDojo } from "./useDojo.tsx";
import useModel from "./useModel.tsx";
import { useSystemCalls } from "./useSystemCalls.ts";
import { useAccount } from "@starknet-react/core";
import { WalletAccount } from "./wallet-account.tsx";
import { HistoricalEvents } from "./historical-events.tsx";

/**
* Global store for managing Dojo game state.
*/
export const useDojoStore = createDojoStore<Schema>();
export const useDojoStore = createDojoStore<SchemaType>();

/**
* Main application component that provides game functionality and UI.
* Handles entity subscriptions, state management, and user interactions.
*
* @param props.sdk - The Dojo SDK instance configured with the game schema
*/
function App({ sdk }: { sdk: SDK<Schema> }) {
function App({ sdk }: { sdk: SDK<SchemaType> }) {
const {
account,
setup: { client },
} = useDojo();
const { account } = useAccount();
const state = useDojoStore((state) => state);
const entities = useDojoStore((state) => state.entities);

const { spawn } = useSystemCalls();

const entityId = useMemo(
() => getEntityIdFromKeys([BigInt(account?.account.address)]),
[account?.account.address]
);
const entityId = useMemo(() => {
if (account) {
return getEntityIdFromKeys([BigInt(account.address)]);
}
return BigInt(0);
}, [account]);

useEffect(() => {
let unsubscribe: (() => void) | undefined;

const subscribe = async () => {
const subscribe = async (account: AccountInterface) => {
const subscription = await sdk.subscribeEntityQuery({
query: new QueryBuilder<Schema>()
query: new QueryBuilder<SchemaType>()
.namespace("dojo_starter", (n) =>
n
.entity("Moves", (e) =>
e.eq(
"player",
addAddressPadding(account.account.address)
addAddressPadding(account.address)
)
)
.entity("Position", (e) =>
e.is(
"player",
addAddressPadding(account.account.address)
addAddressPadding(account.address)
)
)
)
.build(),
callback: (response) => {
if (response.error) {
console.error(
"Error setting up entity sync:",
response.error
);
callback: ({ error, data }) => {
if (error) {
console.error("Error setting up entity sync:", error);
} else if (
response.data &&
response.data[0].entityId !== "0x0"
data &&
(data[0] as ParsedEntity<SchemaType>).entityId !== "0x0"
) {
console.log("subscribed", response.data[0]);
state.updateEntity(response.data[0]);
state.updateEntity(data[0] as ParsedEntity<SchemaType>);
}
},
});

unsubscribe = () => subscription.cancel();
};

subscribe();
if (account) {
subscribe(account);
}

return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [sdk, account?.account.address]);
}, [sdk, account]);

useEffect(() => {
const fetchEntities = async () => {
const fetchEntities = async (account: AccountInterface) => {
try {
await sdk.getEntities({
query: new QueryBuilder<Schema>()
query: new QueryBuilder<SchemaType>()
.namespace("dojo_starter", (n) =>
n.entity("Moves", (e) =>
e.eq(
"player",
addAddressPadding(account.account.address)
addAddressPadding(account.address)
)
)
)
Expand All @@ -107,7 +119,9 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
return;
}
if (resp.data) {
state.setEntities(resp.data);
state.setEntities(
resp.data as ParsedEntity<SchemaType>[]
);
}
},
});
Expand All @@ -116,53 +130,18 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
}
};

fetchEntities();
}, [sdk, account?.account.address]);
if (account) {
fetchEntities(account);
}
}, [sdk, account]);

const moves = useModel(entityId, Models.Moves);
const position = useModel(entityId, Models.Position);
const moves = useModel(entityId as string, ModelsMapping.Moves);
const position = useModel(entityId as string, ModelsMapping.Position);

return (
<div className="bg-black min-h-screen w-full p-4 sm:p-8">
<div className="max-w-7xl mx-auto">
<button
className="mb-4 px-4 py-2 bg-blue-600 text-white text-sm sm:text-base rounded-md hover:bg-blue-700 transition-colors duration-300"
onClick={() => account?.create()}
>
{account?.isDeploying
? "Deploying Burner..."
: "Create Burner"}
</button>

<div className="bg-gray-800 shadow-md rounded-lg p-4 sm:p-6 mb-6 w-full max-w-md">
<div className="text-lg sm:text-xl font-semibold mb-4 text-white">{`Burners Deployed: ${account.count}`}</div>
<div className="mb-4">
<label
htmlFor="signer-select"
className="block text-sm font-medium text-gray-300 mb-2"
>
Select Signer:
</label>
<select
id="signer-select"
className="w-full px-3 py-2 text-base text-gray-200 bg-gray-700 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={account ? account.account.address : ""}
onChange={(e) => account.select(e.target.value)}
>
{account?.list().map((account, index) => (
<option value={account.address} key={index}>
{account.address}
</option>
))}
</select>
</div>
<button
className="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 text-base rounded transition duration-300 ease-in-out"
onClick={() => account.clear()}
>
Clear Burners
</button>
</div>
<WalletAccount />

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
<div className="bg-gray-700 p-4 rounded-lg shadow-inner">
Expand All @@ -185,7 +164,9 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
: "Need to Spawn"}
</div>
<div className="col-span-3 text-center text-base text-white">
{moves && moves.last_direction}
{moves && moves.last_direction.isSome()
? moves.last_direction.unwrap()
: ""}
</div>
</div>
</div>
Expand All @@ -194,22 +175,22 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
<div className="grid grid-cols-3 gap-2 w-full h-48">
{[
{
direction: "Up" as const,
direction: Direction.Up,
label: "↑",
col: "col-start-2",
},
{
direction: "Left" as const,
direction: Direction.Left,
label: "←",
col: "col-start-1",
},
{
direction: "Right" as const,
direction: Direction.Right,
label: "→",
col: "col-start-3",
},
{
direction: "Down" as const,
direction: Direction.Down,
label: "↓",
col: "col-start-2",
},
Expand All @@ -218,10 +199,10 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
className={`${col} h-12 w-12 bg-gray-600 rounded-full shadow-md active:shadow-inner active:bg-gray-500 focus:outline-none text-2xl font-bold text-gray-200`}
key={direction}
onClick={async () => {
await client.actions.move({
account: account.account,
direction: { type: direction },
});
await client.actions.move(
account!,
direction
);
Comment on lines +202 to +205
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check for uninitialized account in move calls.
onClick directly calls client.actions.move(account!, direction). If account is not set or an error occurs, it may break. Replace the assertion ! operator with safer checks or error boundaries.

}}
>
{label}
Expand Down Expand Up @@ -265,6 +246,10 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
entity.models.dojo_starter.Position;
const moves =
entity.models.dojo_starter.Moves;
const lastDirection =
moves?.last_direction?.isSome()
? moves.last_direction?.unwrap()
: "N/A";

return (
<tr
Expand All @@ -278,20 +263,23 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
{position?.player ?? "N/A"}
</td>
<td className="border border-gray-700 p-2">
{position?.vec?.x ?? "N/A"}
{position?.vec?.x.toString() ??
"N/A"}
</td>
<td className="border border-gray-700 p-2">
{position?.vec?.y ?? "N/A"}
{position?.vec?.y.toString() ??
"N/A"}
</td>
<td className="border border-gray-700 p-2">
{moves?.can_move?.toString() ??
"N/A"}
</td>
<td className="border border-gray-700 p-2">
{moves?.last_direction ?? "N/A"}
{lastDirection}
</td>
<td className="border border-gray-700 p-2">
{moves?.remaining ?? "N/A"}
{moves?.remaining?.toString() ??
"N/A"}
</td>
</tr>
);
Expand All @@ -300,6 +288,9 @@ function App({ sdk }: { sdk: SDK<Schema> }) {
</tbody>
</table>
</div>

{/* // Here sdk is passed as props but this can be done via contexts */}
<HistoricalEvents sdk={sdk} />
</div>
</div>
);
Expand Down
8 changes: 4 additions & 4 deletions examples/example-vite-react-sdk/src/DojoContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { Account } from "starknet";
import { dojoConfig } from "../dojoConfig";
import { DojoProvider } from "@dojoengine/core";
import { client } from "./contracts.gen";
import { setupWorld } from "./typescript/contracts.gen";

/**
* Interface defining the shape of the Dojo context.
Expand All @@ -16,7 +16,7 @@ interface DojoContextType {
/** The master account used for administrative operations */
masterAccount: Account;
/** The Dojo client instance */
client: ReturnType<typeof client>;
client: ReturnType<typeof setupWorld>;
/** The current burner account information */
account: BurnerAccount;
}
Expand Down Expand Up @@ -58,7 +58,7 @@ export const DojoContextProvider = ({
dojoConfig.masterPrivateKey,
"1"
),
[]
[dojoProvider.provider]
);

const burnerManagerData = useBurnerManager({ burnerManager });
Expand All @@ -67,7 +67,7 @@ export const DojoContextProvider = ({
<DojoContext.Provider
value={{
masterAccount,
client: client(dojoProvider),
client: setupWorld(dojoProvider),
account: {
...burnerManagerData,
account: burnerManagerData.account || masterAccount,
Expand Down
Loading
Loading