Add a system
In this tutorial you add a system to decrement the counter and update the application to use it.
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/Tables.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/Tables.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 application
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:import { ClientComponents } from "./createClientComponents"; import { SetupNetworkResult } from "./setupNetwork"; export type SystemCalls = ReturnType<typeof createSystemCalls>; export function createSystemCalls( { worldSend, txReduced$, singletonEntity }: SetupNetworkResult, { Counter }: ClientComponents ) { const increment = async () => { const tx = await worldSend("increment", []); await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash); return getComponentValue(Counter, singletonEntity); }; const decrement = async () => { const tx = await worldSend("decrement", []); await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash); 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 worldSend("decrement", []);
This is the way we call functions in top-level systems in a world. The second parameter, the list, is for the function parameters. In this case there aren't any, so it is empty.
await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash);
Await until we receive confirmation that the transaction has been added to a block.
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:import { mount as mountDevTools } from "@latticexyz/dev-tools"; import { setup } from "./mud/setup"; const { components, systemCalls: { decrement, increment }, } = 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"); }); // Just for demonstration purposes: we create a global function that can be // called to invoke the Increment system contract via the world. (See IncrementSystem.sol.) (window as any).increment = async () => { console.log("new counter value:", await increment()); }; (window as any).decrement = async () => { console.log("new counter value:", await decrement()); }; mountDevTools();
Explanation
const { components, systemCalls: { decrement, increment }, } = await setup();
This syntax means the we call
setup()
(opens in a new tab), and then setcomponents
,systemCalls.increment
, andsystemCalls.decrement
to the values provided in the hash returned by this function.systemCalls
comes fromcreateSystemCalls()
, which we modified in the previous step.(window as any).decrement = async () => { console.log("new counter value:", await 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 add it towindow
which is a global variable. -
Modify
packages/client/index.html
to add a decrement button. This is the file after the changes:<!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 onclick="window.increment()">Increment</button> <button onclick="window.decrement()">Decrement</button> </body> </html>
Explanation
<button onclick="window.decrement()">Decrement</button>
Create a
button
(opens in a new tab) with anonClick
(opens in a new tab) property. -
Reload the application to see that there is a decrement button and that you can use it.