diff --git a/docs/pages/config/reference.mdx b/docs/pages/config/reference.mdx
index 17c45833d8..da2019e73a 100644
--- a/docs/pages/config/reference.mdx
+++ b/docs/pages/config/reference.mdx
@@ -121,13 +121,26 @@ The following options are available in both single- and multiple-namespace modes
+
+
A list of modules to install into the world during the deploy step.
Relative path to the module's compiled JSON artifact (usually in `out`) or an import path if using a module from an npm package. This path is resolved using [Node's module `require` API](https://nodejs.org/api/modules.html#modulerequireid).
+
Whether or not to install this as a root module. Defaults to `false`.
- A list of arguments used to call the module's install function.
+
+
+ A list of arguments used to call the module's install function. Each argument is a structure with two fields:
+
+ Solidity data type.
+
+ The value.
+ To encode a complex data type, such as a structure or an array, you can use Viem's [`encodeAbiParameters`](https://viem.sh/docs/abi/encodeAbiParameters.html).
+
+
+
diff --git a/docs/pages/world/modules.mdx b/docs/pages/world/modules.mdx
index ce0b364bff..6f9487d786 100644
--- a/docs/pages/world/modules.mdx
+++ b/docs/pages/world/modules.mdx
@@ -1,3 +1,5 @@
+import { CollapseCode } from "../../components/CollapseCode";
+
# Modules
Modules are onchain installation scripts that create resources and their associated configuration when called by a `World`.
@@ -5,6 +7,170 @@ This is somewhat similar to one of the use cases for [foundry scripts](https://b
## Module installation
+The easiest way to install modules is to edit [the config file](/config/reference#modules).
+
+For example, here is a modified config file that installs [the ERC-721 module](/world/modules/erc721).
+Note that you also need to install the `viem` package in `packages/contracts` to use it.
+
+
+
+```typescript filename="mud.config.ts" showLineNumbers copy {2,4-13,25-41}
+import { defineWorld } from "@latticexyz/world";
+import { encodeAbiParameters } from "viem";
+
+const erc721ModuleArgs = encodeAbiParameters(
+ [
+ { type: "bytes14" },
+ {
+ type: "tuple",
+ components: [{ type: "string" }, { type: "string" }, { type: "string" }],
+ },
+ ],
+ ["0x44444444".padEnd(30, "0"), ["No Valuable Token", "NVT", "http://www.example.com/base/uri/goes/here"]],
+);
+
+export default defineWorld({
+ namespace: "app",
+ tables: {
+ Counter: {
+ schema: {
+ value: "uint32",
+ },
+ key: [],
+ },
+ },
+ modules: [
+ {
+ artifactPath: "@latticexyz/world-modules/out/PuppetModule.sol/PuppetModule.json",
+ root: false,
+ args: [],
+ },
+ {
+ artifactPath: "@latticexyz/world-modules/out/ERC721Module.sol/ERC721Module.json",
+ root: false,
+ args: [
+ {
+ type: "bytes",
+ value: erc721ModuleArgs,
+ },
+ ],
+ },
+ ],
+});
+```
+
+
+
+
+
+Explanation
+
+```typescript
+import { encodeAbiParameters } from "viem";
+```
+
+In simple cases it is enough to use the config parser to specify the module arguments.
+However, the NFT module requires a `struct` as [one of the arguments](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol#L37).
+We use [`encodeAbiParameters`](https://viem.sh/docs/abi/encodeAbiParameters.html) to encode the `struct` data.
+
+Note that this means we need to issue `pnpm install viem` in `packages/contracts` to be able to use the library here.
+
+```typescript
+const erc721ModuleArgs = encodeAbiParameters(
+```
+
+You can see the arguments for the ERC-721 module [here](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol#L37).
+There are two arguments:
+
+- A 14-byte identifier for the namespace.
+- An `ERC721MetadataData` for the ERC-721 parameters, [defined here](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol#L19-L23).
+
+However, the arguments for a module are [ABI encoded](https://docs.soliditylang.org/en/develop/abi-spec.html) to a single value of type `bytes`.
+So we use `encodeAbiParameters` from the viem library to create this argument.
+The first parameter of this function is a list of argument types.
+
+```typescript
+ [
+ { type: "bytes14" },
+```
+
+The first parameter is simple, a 14 byte value for the namespace.
+
+```typescript
+ {
+ type: "tuple",
+ components: [{ type: "string" }, { type: "string" }, { type: "string" }],
+ },
+ ],
+```
+
+The second value is more complicated, it's a struct, or as it is called in ABI, a tuple.
+It consists of three strings (the token name, symbol, and [base URI](https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721-baseURI--)).
+
+```typescript
+ [
+ "0x44444444".padEnd(30, "0"),
+```
+
+The second `encodeAbiParameters` parameter is a list of the values, of the types declared in the first list.
+
+The first parameter for the module is `bytes14`, which is expected to be a hexadecimal value with twenty-eight hexadecimal digits, for a total length of thirty.
+Here we use `0x4...40....0` with eight 4's followed by twenty 0's.
+This gives us the namespace `DDDD`, which is easy to recognize both as hex and as text.
+
+```typescript
+ ["No Valuable Token", "NVT", "http://www.example.com/base/uri/goes/here"],
+ ],
+);
+```
+
+The second parameter for the module is a structure of three strings, so here we provide the three strings.
+Then we close all the definitions.
+
+```typesceript
+ modules: [
+ {
+ artifactPath: "@latticexyz/world-modules/out/PuppetModule.sol/PuppetModule.json",
+ root: false,
+ args: [],
+ },
+```
+
+A module declaration requires three parameters:
+
+- `artifactPath`, a link to the compiled JSON file for the module.
+- `root`, whether to install the module with [root namespace permissions](/world/systems#root-systems) or not.
+- `args` the module arguments.
+
+Here we install [the `puppet` module](https://github.com/latticexyz/mud/tree/main/packages/world-modules/src/modules/puppet).
+We need this module because a `System` is supposed to be stateless, and easily upgradeable to a contract in a different address.
+However, both the [ERC-20 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) and the [ERC-721 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) require the token contract to emit events.
+The solution is to put the `System` in one contract and have another contract, the puppet, which receives requests and emits events according to the ERC.
+
+```typescript
+ {
+ artifactPath: "@latticexyz/world-modules/out/ERC721Module.sol/ERC721Module.json",
+ root: false,
+ args: [
+ {
+ type: "bytes",
+```
+
+The data type for this parameter is `bytes`, because it is treated as opaque bytes by the `World` and only gets parsed by the module after it is transferred.
+
+```typescript
+ value: erc721ModuleArgs,
+ },
+ ],
+ },
+```
+
+The module arguments, stored in `erc721ModuleArgs`.
+
+
+
+### Installation by script
+
Modules can be installed using [`World.installModule(address moduleAddress, bytes memory initData)`](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/ModuleInstallationSystem.sol#L17-L37).
When you do this, the `World` calls the module's `install` function with `initData` as the argument(s).
Because this is a call, the module does not have any administrative permissions on the `World`.
@@ -12,6 +178,69 @@ Because this is a call, the module does not have any administrative permissions
Alternatively, the owner of the root namespace can install modules using [`World.installRootModule(address moduleAddress, bytes memory initData)`](https://github.com/latticexyz/mud/blob/main/packages/world/src/World.sol#L90-L119).
In this case, the `World` uses delegatecall and module has all the permissions of a `System` in the root namespace.
+For example, here is an installation script for [the `KeysWithValue` module](/world/modules/keyswithvalue).
+
+
+
+```solidity filename="DeployKeyWithValueModule.s.sol" copy showLineNumbers {22-24}
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.21;
+
+import { Script } from "forge-std/Script.sol";
+import { console } from "forge-std/console.sol";
+
+import { IWorld } from "../src/codegen/world/IWorld.sol";
+import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+import { RESOURCE_TABLE } from "@latticexyz/world/src/worldResourceTypes.sol";
+import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
+import { KeysWithValueModule } from "@latticexyz/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol";
+
+contract DeployKeyWithValueModule is Script {
+ function run() external {
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ address worldAddress = 0xC14fBdb7808D9e2a37c1a45b635C8C3fF64a1cc1;
+
+ vm.startBroadcast(deployerPrivateKey);
+ IWorld world = IWorld(worldAddress);
+
+ // Deploy the module
+ KeysWithValueModule keysWithValueModule = new KeysWithValueModule();
+ ResourceId sourceTableId = WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: "", name: "Tasks" });
+ world.installRootModule(keysWithValueModule, abi.encode(sourceTableId));
+
+ vm.stopBroadcast();
+ }
+}
+```
+
+
+
+
+
+Explanation
+
+```solidity
+ KeysWithValueModule keysWithValueModule = new KeysWithValueModule();
+```
+
+Deploy the module.
+Modules are stateless, so if there is already a copy of the contract on the blockchain you can just use that.
+
+```solidity
+ ResourceId sourceTableId =
+ WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: "", name: "Tasks" });
+```
+
+Get the resourceID for the table that needs a reverse index.
+
+```solidity
+ world.installRootModule(keysWithValueModule, abi.encode(sourceTableId));
+```
+
+Actually install the module in the `World`.
+
+
+
## Writing modules
The common use for a module is to add functionality to a `World`.
diff --git a/docs/pages/world/modules/erc721.mdx b/docs/pages/world/modules/erc721.mdx
index d3b46e97b7..0fd447899a 100644
--- a/docs/pages/world/modules/erc721.mdx
+++ b/docs/pages/world/modules/erc721.mdx
@@ -18,10 +18,21 @@ The easiest way to deploy this module is to edit `mud.config.ts`.
-```typescript filename="mud.config.ts" copy {2,14-50}
+```typescript filename="mud.config.ts" showLineNumbers copy {2-16,28-44}
import { defineWorld } from "@latticexyz/world";
import { encodeAbiParameters } from "viem";
+const erc721ModuleArgs = encodeAbiParameters(
+ [
+ { type: "bytes14" },
+ {
+ type: "tuple",
+ components: [{ type: "string" }, { type: "string" }, { type: "string" }],
+ },
+ ],
+ ["0x44444444".padEnd(30, "0"), ["No Valuable Token", "NVT", "http://www.example.com/base/uri/goes/here"]],
+);
+
export default defineWorld({
namespace: "app",
tables: {
@@ -44,24 +55,12 @@ export default defineWorld({
args: [
{
type: "bytes",
- value: encodeAbiParameters(
- [
- { type: "bytes14" },
- {
- type: "tuple",
- components: [{ type: "string" }, { type: "string" }, { type: "string" }],
- },
- ], // end of list of types
- [
- "0x44444444".padEnd(30, "0"),
- ["No Valuable Token", "NVT", "http://www.example.com/base/uri/goes/here"], // end of the ERC-721 metadata tuple
- ], // end of parameter list
- ), // end of encodeAbiParameters call
- }, // end of the argument
- ], // end of list of args for the module
- }, // end of module definition for the ERC-721 module
- ], // end of module list
-}); // end of defineWorld call
+ value: erc721ModuleArgs,
+ },
+ ],
+ },
+ ],
+});
```
@@ -80,31 +79,8 @@ We use [`encodeAbiParameters`](https://viem.sh/docs/abi/encodeAbiParameters.html
Note that this means we need to issue `pnpm install viem` in `packages/contracts` to be able to use the library here.
-```typesceript
- modules: [
- {
- artifactPath: "@latticexyz/world-modules/out/PuppetModule.sol/PuppetModule.json",
- root: false,
- args: [],
- },
-```
-
-A module declaration requires three parameters:
-
-- `artifactPath`, a link to the compiled JSON file for the module.
-- `root`, whether to install the module with [root namespace permissions](/world/systems#root-systems) or not.
-- `args` the module arguments.
-
-Here we install [the `puppet` module](https://github.com/latticexyz/mud/tree/main/packages/world-modules/src/modules/puppet).
-We need this module because a `System` is supposed to be stateless, and easily upgradeable to a contract in a different address.
-However, both the [ERC-20 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) and the [ERC-721 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) require the token contract to emit events.
-The solution is to put the `System` in one contract and have another contract, the puppet, which receives requests and emits events according to the ERC.
-
```typescript
- {
- artifactPath: "@latticexyz/world-modules/out/ERC721Module.sol/ERC721Module.json",
- root: false,
- args: [
+const erc721ModuleArgs = encodeAbiParameters(
```
You can see the arguments for the ERC-721 module [here](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol#L37).
@@ -114,47 +90,30 @@ There are two arguments:
- An `ERC721MetadataData` for the ERC-721 parameters, [defined here](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc721-puppet/tables/ERC721Metadata.sol#L19-L23).
However, the arguments for a module are [ABI encoded](https://docs.soliditylang.org/en/develop/abi-spec.html) to a single value of type `bytes`.
-
-```typescript
- {
- type: "bytes",
-```
-
-The data type for this parameter is `bytes`, because it is treated as opaque bytes by the `World` and only gets parsed by the module after it is transferred.
-
-```typescript
- value: encodeAbiParameters(
- [
-```
-
-Use `encodeAbiParameters` from the viem library to create the argument.
+So we use `encodeAbiParameters` from the viem library to create this argument.
The first parameter of this function is a list of argument types.
```typescript
- [
- { type: 'bytes14' },
+ [
+ { type: "bytes14" },
```
The first parameter is simple, a 14 byte value for the namespace.
```typescript
- {
- type: 'tuple',
- components: [
- { "type": "string" },
- { "type": "string" },
- { "type": "string" },
- ]
- }
- ], // end of list of types
+ {
+ type: "tuple",
+ components: [{ type: "string" }, { type: "string" }, { type: "string" }],
+ },
+ ],
```
The second value is more complicated, it's a struct, or as it is called in ABI, a tuple.
It consists of three strings (the token name, symbol, and [base URI](https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721-baseURI--)).
```typescript
- [
- "0x44444444".padEnd(30, "0"),
+ [
+ "0x44444444".padEnd(30, "0"),
```
The second `encodeAbiParameters` parameter is a list of the values, of the types declared in the first list.
@@ -164,22 +123,53 @@ Here we use `0x4...40....0` with eight 4's followed by twenty 0's.
This gives us the namespace `DDDD`, which is easy to recognize both as hex and as text.
```typescript
-["No Valuable Token", "NVT", "http://www.example.com/base/uri/goes/here"]; // end of the ERC-721 metadata tuple
+ ["No Valuable Token", "NVT", "http://www.example.com/base/uri/goes/here"],
+ ],
+);
```
The second parameter for the module is a structure of three strings, so here we provide the three strings.
+Then we close all the definitions.
+
+```typesceript
+ modules: [
+ {
+ artifactPath: "@latticexyz/world-modules/out/PuppetModule.sol/PuppetModule.json",
+ root: false,
+ args: [],
+ },
+```
+
+A module declaration requires three parameters:
+
+- `artifactPath`, a link to the compiled JSON file for the module.
+- `root`, whether to install the module with [root namespace permissions](/world/systems#root-systems) or not.
+- `args` the module arguments.
+
+Here we install [the `puppet` module](https://github.com/latticexyz/mud/tree/main/packages/world-modules/src/modules/puppet).
+We need this module because a `System` is supposed to be stateless, and easily upgradeable to a contract in a different address.
+However, both the [ERC-20 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) and the [ERC-721 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) require the token contract to emit events.
+The solution is to put the `System` in one contract and have another contract, the puppet, which receives requests and emits events according to the ERC.
+
+```typescript
+ {
+ artifactPath: "@latticexyz/world-modules/out/ERC721Module.sol/ERC721Module.json",
+ root: false,
+ args: [
+ {
+ type: "bytes",
+```
+
+The data type for this parameter is `bytes`, because it is treated as opaque bytes by the `World` and only gets parsed by the module after it is transferred.
```typescript
- ] // end of parameter list
- ) // end of encodeAbiParameters call
- } // end of the argument
- ], // end of list of args for the module
- } // end of module definition for the ERC-721 module
- ] // end of module list
-}); // end of defineWorld call
+ value: erc721ModuleArgs,
+ },
+ ],
+ },
```
-And then we need to finish all the definitions.
+The module arguments, stored in `erc721ModuleArgs`.