First hour
One path: Foundry project, annotate, generate, ship, point the SDK at the URL it printed. Do these steps in order.
Do these in order, from a Foundry project that already compiles with forge build. A project without forge-std dies on script/ before Interlude says anything useful.
1. Install the CLI in Foundry, not the SDK
npm i -D @interludelayer-sdk/cli
npx @interludelayer-sdk/cli initThat first init prints a starter and exits 1. That is expected: nothing inherits Delegatable yet. It also wrote lib/interlude and the remapping. Always call the scoped binary. A bare interlude is not on PATH after npx.
2. Add the contract, pragma included
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Delegatable} from "@interludelayer/contracts/Delegatable.sol";
import {IInterludeHub} from "@interludelayer/contracts/interfaces/IInterludeHub.sol";
import {Types} from "@interludelayer/contracts/interfaces/Types.sol";
contract YourApp is Delegatable {
/// @custom:interlude global
uint256 internal score;
constructor(IInterludeHub hub_) Delegatable(hub_) {}
function play() external whenNotDelegated(Types.GLOBAL) {
score += 1;
}
function currentScore() external view returns (uint256) {
return score;
}
}The comment is what the generator reads. The view is so the frontend can read the number. An internal score has no ABI getter.
What Types.GLOBAL is
Types.sol is the Hub’s shared structs. For the first hour you only need one constant: Types.GLOBAL, the partition for the whole contract. whenNotDelegated(Types.GLOBAL) is the lock. Without it, Monad still accepts a write the node also holds, and the next commit stalls. Per-user partitions are keyOf(user) later. You do not open the rest of that file to ship.
3. Generate the surface
npx @interludelayer-sdk/cli gen --contract YourAppWrites YourAppInterludeSurface.sol beside the source. Same-directory Solidity is not auto-imported. Keep the Delegatable import. The constructor still names Delegatable(hub_). Dropping it does not compile.
import {Delegatable} from "@interludelayer/contracts/Delegatable.sol";
import {IInterludeHub} from "@interludelayer/contracts/interfaces/IInterludeHub.sol";
import {Types} from "@interludelayer/contracts/interfaces/Types.sol";
import {YourAppInterludeSurface} from "./YourAppInterludeSurface.sol";
contract YourApp is YourAppInterludeSurface {
/// @custom:interlude global
uint256 internal score;
constructor(IInterludeHub hub_) Delegatable(hub_) {
_registerInterludeSurface();
}
function play() external whenNotDelegated(Types.GLOBAL) {
score += 1;
}
function currentScore() external view returns (uint256) {
return score;
}
}4. Config, check, ship
npx @interludelayer-sdk/cli init --contract YourApp
npx @interludelayer-sdk/cli check
npx @interludelayer-sdk/cli shipinit writes interlude.toml. It succeeds even if you forgot to inherit the surface. check only compares layouts, it does not prove you registered. ship deploys on Monad testnet and prints app plus node. A 502 for a few minutes is the image building. Do not point the SDK at https://rpc.interludelayer.xyz.
5. Frontend, a different folder
npm i @interludelayer-sdk/sdk viemimport { 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", // printed by ship
abi: yourAppAbi,
node: "https://YOUR_NODE", // printed by ship. Not the public demo.
base: createPublicClient({
chain: monadTestnet,
transport: http("https://testnet-rpc.monad.xyz"),
}),
});
const status = await interlude.status();
if (status.app.toLowerCase() !== interlude.app.toLowerCase()) {
throw new Error("this node is serving a different contract");
}
const session = await interlude.openSession({ wallet, scope: ["play"] });
await session.send("play");
await interlude.read("currentScore");Next: Point it at your app. The public 3D floor is a different pair, on The demo app.
interlude dev is the laptop loop. The published CLI does not ship the node binary. Local port in interlude.toml is 8555, not 8546.
