Add a system
In this tutorial you add a system to decrement the counter and update the application to use it.
Setup
Create a new MUD application from the template. Use the vanilla template.
Add a contract for the new system
Create a file packages/contracts/src/systems/DecrementSystem.sol
.
Note that we could have just added a function to the existing system, IncrementSystem.sol
.
The only reason we are adding a new system here is to see how to do it.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { System } from "@latticexyz/world/src/System.sol";
import { Counter } from "../codegen/index.sol";
contract DecrementSystem is System {
function decrement() public returns (uint32) {
uint32 counter = Counter.get();
uint32 newValue = counter - 1;
Counter.set(newValue);
return newValue;
}
}
Explanation
import { System } from "@latticexyz/world/src/System.sol";
import { Counter } from "../codegen/index.sol";
The two things the system needs to know: how to be a System
and how to access the Counter
.
uint32 counter = Counter.get();
Get the counter value.
Counter.set(newValue);
Set the counter to a new value.
Add decrement
to the user interface
Having a system be able to do something doesn't help anybody unless it is called from somewhere. In this case, the vanilla getting started front end.
-
Edit
packages/client/src/mud/createSystemCalls.ts
to includedecrement
. This is the file after the changes:createSystemCalls.ts/* * Create the system calls that the client can use to ask * for changes in the World state (using the System contracts). */ import { getComponentValue } from "@latticexyz/recs"; import { ClientComponents } from "./createClientComponents"; import { SetupNetworkResult } from "./setupNetwork"; import { singletonEntity } from "@latticexyz/store-sync/recs"; export type SystemCalls = ReturnType<typeof createSystemCalls>; export function createSystemCalls( /* * The parameter list informs TypeScript that: * * - The first parameter is expected to be a * SetupNetworkResult, as defined in setupNetwork.ts * * Out of this parameter, we only care about two fields: * - worldContract (which comes from getContract, see * https://github.com/latticexyz/mud/blob/main/templates/vanilla/packages/client/src/mud/setupNetwork.ts#L63-L69). * * - waitForTransaction (which comes from syncToRecs, see * https://github.com/latticexyz/mud/blob/main/templates/vanilla/packages/client/src/mud/setupNetwork.ts#L77-L83). * * - From the second parameter, which is a ClientComponent, * we only care about Counter. This parameter comes to use * through createClientComponents.ts, but it originates in * syncToRecs * (https://github.com/latticexyz/mud/blob/main/templates/vanilla/packages/client/src/mud/setupNetwork.ts#L77-L83). */ { worldContract, waitForTransaction }: SetupNetworkResult, { Counter }: ClientComponents, ) { const increment = async () => { /* * Because IncrementSystem * (https://mud.dev/templates/typescript/contracts#incrementsystemsol) * is in the root namespace, `.increment` can be called directly * on the World contract. */ const tx = await worldContract.write.increment(); await waitForTransaction(tx); return getComponentValue(Counter, singletonEntity); }; const decrement = async () => { const tx = await worldContract.write.decrement(); await waitForTransaction(tx); return getComponentValue(Counter, singletonEntity); }; return { increment, decrement, }; }
Explanation
The new function is
decrement
.const decrement = async () => {
This function involves sending a transaction, which is a slow process, so it needs to be asynchronous (opens in a new tab).
const tx = await worldContract.write.decrement();
This is the way we call functions in systems in the root namespace of the world.
await waitForTransaction(tx);
Await until the transaction has been included in a block and the corresponding state has been synchronized with the client.
return getComponentValue(Counter, singletonEntity) };
Get the value of
Counter
to return it. It should already be the updated value.return { increment, decrement, };
Of course, we also need to return
decrement
so it can be used elsewhere. -
Update
packages/client/src/index.ts
to includedecrement
. This is the file after the changes:index.tsimport { setup } from "./mud/setup"; import mudConfig from "contracts/mud.config"; const { components, systemCalls: { increment, decrement }, network, } = await setup(); // Components expose a stream that triggers when the component is updated. components.Counter.update$.subscribe((update) => { const [nextValue, prevValue] = update.value; console.log("Counter updated", update, { nextValue, prevValue }); document.getElementById("counter")!.innerHTML = String(nextValue?.value ?? "unset"); }); // Attach the increment function to the html element with ID `incrementButton` (if it exists) document.querySelector("#incrementButton")?.addEventListener("click", increment); document.querySelector("#decrementButton").addEventListener("click", decrement); // https://vitejs.dev/guide/env-and-mode.html if (import.meta.env.DEV) { const { mount: mountDevTools } = await import("@latticexyz/dev-tools"); mountDevTools({ config: mudConfig, publicClient: network.publicClient, walletClient: network.walletClient, latestBlock$: network.latestBlock$, storedBlockLogs$: network.storedBlockLogs$, worldAddress: network.worldContract.address, worldAbi: network.worldContract.abi, write$: network.write$, recsWorld: network.world, }); }
Explanation
const { components, systemCalls: { decrement, increment }, network, } = await setup();
This syntax means we call
setup()
(opens in a new tab) and then set variables to portions of the result.components
←(await setup()).components
increment
←(await setup()).systemCalls.increment
decrement
←(await setup()).systemCalls.decrement
network
←(await setup()).network
systemCalls
comes fromcreateSystemCalls()
, which we modified in the previous step.document.querySelector("#decrementButton").addEventListener("click", decrement);
We need to make
decrement
available to our application code. Most frameworks have a standard mechanism to do this, but we are usingvanilla
, which doesn't. So we usedocument.querySelector
(opens in a new tab) to find the appropriate button and thenaddEventListener
(opens in a new tab) to registerdecrement
. -
Modify
packages/client/index.html
to add a decrement button. This is the file after the changes:index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>a minimal MUD client</title> </head> <body> <script type="module" src="/src/index.ts"></script> <div>Counter: <span id="counter">0</span></div> <button id="incrementButton">Increment</button> <button id="decrementButton">Decrement</button> </body> </html>
-
See that there is a decrement button and that you can use it.