World
Keys in Table

Keys in Table module

The KeysInTable module (opens in a new tab) tracks which keys are used with a specific table. This is useful if you need to be able to iterate through all the records of a table onchain.

💰

Using this module will add gas overhead to every write in the table it's used with. You can reduce this overhead by implementing your own "onchain index" pattern.

Deployment

It can be deployed multiple times, each time for a different table. For example, here is a mud.config.ts that deploys it for the Tasks table in the React template (opens in a new tab).

mud.config.ts
import { defineWorld } from "@latticexyz/world";
import { resourceToHex } from "@latticexyz/common";
 
export default defineWorld({
  namespace: "app",
  tables: {
    Tasks: {
      schema: {
        id: "bytes32",
        createdAt: "uint256",
        completedAt: "uint256",
        description: "string",
      },
      key: ["id"],
    },
  },
  modules: [
    {
      root: true,
      artifactPath: "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json",
      args: [
        {
          type: "bytes32",
          value: resourceToHex({
            type: "table",
            namespace: "app",
            name: "Tasks",
          }),
        },
      ],
    },
  ],
});
Explanation
import { resourceToHex } from "@latticexyz/common";

We need to provide KeysInTable with the resource ID for the table.

  modules: [
    {

To install modules using mud.config.ts, you specify modules: which contains a list of module defintions.

      root: true,
      artifactPath: "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json",
      args: [
  • artifactPath, a link to the compiled JSON file for the module.
  • root, whether to install the module with root namespace permissions or not.
  • args the module arguments.
        {
          type: "bytes32",
          value:

The args list contains structures. Each structure has two fields:

resourceToHex({
  type: "table",
  namespace: "app",
  name: "Tasks",
});

This function (opens in a new tab) creates the resourceID for us from the table's data.

        }
      ]
    }
  ]

Usage

Installing the module creates two tables, KeysInTable and UsedKeysIndex. When entries are added in the source table, their keys are written to the arrays in KeysInTable and a hash of the keys is written to keysHash. This only applies from the time the module is installed, entries created prior to that don't get the reverse mapping.

To get the keys you use getKeysInTable (opens in a new tab) with the identifier of the source table.

For example, here is a Solidity script that reads the tasks added to Tasks after module installation.

UseGetKeysInTable.s.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;
 
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
 
import { IWorld } from "../src/codegen/world/IWorld.sol";
import { getKeysInTable } from "@latticexyz/world-modules/src/modules/keysintable/getKeysInTable.sol";
import { Tasks, TasksData } from "../src/codegen/index.sol";
 
contract UseGetKeysInTable is Script {
  function run() external {
    uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
    address worldAddress = vm.envAddress("WORLD_ADDRESS");
    IWorld world = IWorld(worldAddress);
    StoreSwitch.setStoreAddress(worldAddress);
 
    vm.startBroadcast(deployerPrivateKey);
    world.app__addTask("Walk the cat");
    world.app__addTask("Cook delicious meal");
    world.app__addTask("Practice Elvish");
    vm.stopBroadcast();
 
    bytes32[][] memory keys = getKeysInTable(Tasks._tableId);
 
    console.log("Number of keys:", keys.length);
    for (uint i = 0; i < keys.length; i++) {
      console.log("\n");
      console.log("Key #", i, ":");
      for (uint j = 0; j < keys[i].length; j++) {
        console.logBytes32(keys[i][j]);
      }
    }
 
    console.log("\n\n");
    console.log("Task descriptions");
    for (uint i = 0; i < keys.length; i++) {
      TasksData memory task = Tasks.get(keys[i][0]);
      console.log(task.description);
    }
  }
}
Explanation
import { getKeysInTable } from "@latticexyz/world-modules/src/modules/keysintable/getKeysInTable.sol";

The function that gets the keys in the table.

    StoreSwitch.setStoreAddress(worldAddress);

To use getKeysInTable we need to set the store address to worldAddress.

    vm.startBroadcast(deployerPrivateKey);
    world.app__addTask("Walk the cat");
    world.app__addTask("Cook delicious meal");
    world.app__addTask("Practice Elvish");
    vm.stopBroadcast();

Creates three entries with different descriptions, to ensure we have entries to show. Note that this is the only place in the script we need to send transactions. getKeysInTable is a view function (opens in a new tab), and does not change the state.

    bytes32[][] memory keys = getKeysInTable(Tasks._tableId);

Get the key(s) by passing the Tasks table id as a parameter.

    console.log("Number of keys:", keys.length);
    for(uint i=0; i<keys.length; i++) {
       console.log("\n");
       console.log("Key #", i, ":");
       for(uint j=0; j<keys[i].length; j++) {
          console.logBytes32(keys[i][j]);
       }
    }

The getKeysInTable function returns a two-dimensional array. The first dimension is the keys in the table. The second dimension is the different key fields (in this case there is only one).

    console.log("\n\n");
    console.log("Task descriptions:");
    for(uint i=0; i<keys.length; i++) {
       TasksData memory task = Tasks.get(keys[i][0]);
       console.log(task.description);
    }

Once you have the key you can query the rest of the data using Tasks.get. In this example we print all the task descriptions.