Snap Sync

Snap-sync

The SnapSync (opens in a new tab) plugin allows clients to sync with the state of a World by fetching it directly from the blockchain.

Without this plugin or an indexer like MODE, clients can be slow to sync as they have to process all events since the World was deployed.

Let's walk through an example of using snap-sync.

Creating tables

First, we define some tables.

import { mudConfig } from "@latticexyz/world/register";
 
export default mudConfig({
  tables: {
    Karma: "int32",
    Reputation: {
      schema: {
        fame: "uint8",
        infamy: "uint8",
      },
    },
  },
});

Adding the snap-sync plugin

Next, add the SnapSync plugin. This will automatically install the necessary smart contract modules.

import { mudConfig } from "@latticexyz/world/register";
import { resolveTableId } from "@latticexyz/config";
/**
 * Importing this enables "snap sync mode".
 * It allows clients to sync the latest state of the world using view functions.
 */
import "@latticexyz/world/snapsync";
 
export default mudConfig({
  snapSync: true,
  tables: {
    Karma: "int32",
    Reputation: {
      schema: {
        fame: "uint8",
        infamy: "uint8",
      },
    },
  },
});

Using snap-sync in the client

Now that the modules are installed on-chain, we need to enable snap-sync in the client. This depends on your exact client setup, but it comes by default with the MUD templates from Quick start.

With a template

In the standard templates, you can enable snap-sync on a given client by appending ?snapSync=true to the url. Alternatively you can hardcode it to be enabled by default in getNetworkConfig:

export async function getNetworkConfig(): Promise<NetworkConfig> {
 
  ...
 
  return {
    ...
    snapSync: params.get("snapSync") === "true",
  };
}
Manually

When using std-client (opens in a new tab), we usually begin syncing by calling the startSync() function from setupMUDV2Network:

// Start syncing from the initial block set in the MUD config
result.startSync();

Without any arguments, startSync() uses the initial block number in the MUD config, which is usually the block that the World was deployed. The means the initial sync is slow as it processes every event since deployment.

However, startSync() takes two arguments:

  • initialRecords is an initial set of records for each table.
  • initialBlockNumber is the block number to start syncing from.

Using snap-sync, we can fetch the current state directly from the World contract, "skipping" the initial sync phase. The initial records are fetched at the current block number, for every table, using the snap-sync view.

In the client, we fetch all the records at the current block number then begin syncing:

import { getSnapSyncRecords } from "@latticexyz/network";
import { getTableIds } from "@latticexyz/utils";
 
...
 
// Fetch the ID's of all tables in our config
const tableIds = getTableIds(storeConfig);
 
// Fetch the current block number
const currentBlockNumber = await provider.getBlockNumber();
 
// Fetch all records at the current block for all tables
const tableRecords = await getSnapSyncRecords(
  networkConfig.worldAddress,
  tableIds,
  currentBlockNumber,
  signerOrProvider
);
 
// Start syncing with these records at the current block
result.startSync(tableRecords, currentBlockNumber);

If both MODE and snap-sync are enabled, the arguments that are passed into startSync() take precedence. In this case the client would perform the initial "catch up" with snap-sync then stream from MODE.

To allow users to toggle snap-sync, add a flag to the NetworkConfig type:

type NetworkConfig = SetupContractConfig & {
  privateKey: string;
  faucetServiceUrl?: string;
  snapSync?: boolean;
};

To set a default value for the flag, edit getNetworkConfig():

export async function getNetworkConfig(): Promise<NetworkConfig> {
 
  ...
 
  return {
    ...
    snapSync: params.get("snapSync") === "true",
  };
}