FRONTEND

Point it at your app

Four fields: the address ship printed, its ABI, the node URL ship printed, and a Monad client. Never the public demo URL.

Create the client once. Every later call goes through this object. Use the pair ship printed, not the public demo values.

lib/interlude.ts
import { createPublicClient, http } from "viem";
import { monadTestnet } from "viem/chains";
import { createInterludeClient } from "@interludelayer-sdk/sdk";
import { yourAppAbi } from "./your-app-abi";

export const interlude = createInterludeClient({
  app: "0xYOUR_APP",
  abi: yourAppAbi,
  node: "https://YOUR_NODE",
  base: createPublicClient({
    chain: monadTestnet,
    transport: http("https://testnet-rpc.monad.xyz"),
  }),
});

const status = await interlude.status();
// A public demo node answers this and names the demo. It does not throw.
if (status.app.toLowerCase() !== interlude.app.toLowerCase()) {
  throw new Error("wrong node");
}

The four fields

app is the contract on Monad. Same address on the node. abi encodes the call and resolves scope: ["play"]. A typo fails before the wallet opens.

node is the engine for this app. One instance, one contract. https://rpc.interludelayer.xyz is the Paris demo floor. Locally, interlude.toml sets port = 8555.

base is Monad, not the node. The grant’s chain id, the hub address and the session epoch can only be read on chain. A grant signed under the node’s chain id (4242) recovers to nobody. Pass an explicit RPC URL. http() with no URL throws.

Several worlds

createInterludeClient does not pick a node. One shipped app has one URL. If you run several contracts, one per region, call nearestFloor() first and pass that pair. That is the smart router. The helper and the public demo map are on Nearest floor.

The public demo pair is on The demo app, if you are talking to the public demo on purpose.